package com.hyt.it.ogt.pj.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.hyt.common.apiInfor.ApiResponse;
import com.hyt.common.utils.exception.BusinessException;
import com.hyt.common.utils.result.Result;
import com.hyt.core.base.BaseEntity;
import com.hyt.core.base.BaseServiceImpl;
import com.hyt.core.constant.CommonConstant;
import com.hyt.core.util.UUIDUtils;
import com.hyt.it.ogt.common.config.GlobalConfig;
import com.hyt.it.ogt.common.utils.DateUtils;
import com.hyt.it.ogt.common.utils.ExecutorPoolUtil;
import com.hyt.it.ogt.pj.common.Constants;
import com.hyt.it.ogt.pj.common.MarkException;
import com.hyt.it.ogt.pj.common.ResponseCode;
import com.hyt.it.ogt.pj.enums.*;
import com.hyt.it.ogt.pj.feign.KwClient;
import com.hyt.it.ogt.pj.feign.tps.TpsClient;
import com.hyt.it.ogt.pj.mapper.*;
import com.hyt.it.ogt.pj.model.dto.NeedReduceDTO;
import com.hyt.it.ogt.pj.model.dto.StudentExtendDTO;
import com.hyt.it.ogt.pj.model.entity.*;
import com.hyt.it.ogt.pj.model.param.MarkCandidateParam;
import com.hyt.it.ogt.pj.model.param.ProjectInfoParam;
import com.hyt.it.ogt.pj.model.vo.ProjectVo;
import com.hyt.it.ogt.pj.model.vo.StudentAnswerVo;
import com.hyt.it.ogt.pj.model.vo.TaskPageItemVo;
import com.hyt.it.ogt.pj.model.vo.download.*;
import com.hyt.it.ogt.pj.service.*;
import com.hyt.it.ogt.pj.utils.NullHandleUtils;
import com.hyt.model.PageParam;
import com.hyt.model.tps.vo.Bucket;
import com.linuxense.javadbf.DBFDataType;
import com.linuxense.javadbf.DBFField;
import com.linuxense.javadbf.DBFUtils;
import com.linuxense.javadbf.DBFWriter;
import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.CompleteMultipartUploadResult;
import com.obs.services.model.PutObjectResult;
import com.obs.services.model.UploadFileRequest;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * <p>
 * 评卷项目表 服务实现类
 * </p>
 *
 * @author qiaozj
 * @since 2020-07-09
 */
@Service
public class ProjectServiceImpl extends BaseServiceImpl<ProjectMapper, Project> implements IProjectService {

    protected Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    // 导入报名库文件的常量
    private static final String BMK_PREFIX = "bmk";
    private static final String BMK_SUFFIX = ".dbf";
    // 导入客观题成绩文件的常量
    private static final String KGTCJ_PREFIX = "kgtcj";
    private static final String KGTCJ_SUFFIX = ".dbf";
    // 题结构导出常量
    private static final String ITEM_STRUCTURE_PREFIX = "importSTJG";
    // 导入接口文件的常量
    private static final String INTERFACE_PREFIX = "interface";
    private static final String INTERFACE_SUFFIX = ".txt";
    // 图片存储文件夹
    private static final String IMAGE_DIRECTORY = "pictures";
    // 年月日时分的时间格式
    public static final String YYYYMMDDHHMM = "yyyyMMddHHmm";
    // 空格输出
    private static final String SPACE_KEY = "\t";
    // MB上传转换
    private static final Long MB_SPEED_CONVERT = 1024 * 1024L;

    @Value("${download.timeinterval:1}")
    private Integer downloadTimeinterval;

    @Resource
    private KwClient kwClient;

    @Autowired
    private IConfigService configService;

    @Autowired
    private IUserConfigService userConfigService;

    @Autowired
    private ISubjectService subjectService;

    @Autowired
    private IPaperService paperService;

    @Autowired
    private IStudentService studentService;

    @Autowired
    private IStudentAnswerService studentAnswerService;

    @Autowired
    private IPieceService pieceService;

    @Autowired
    private ITeacherTaskInfoService teacherTaskInfoService;

    @Autowired
    private ITaskService taskService;

    @Autowired
    private IPieceItemService pieceItemService;

    @Autowired
    private IPaperItemService paperItemService;

    @Autowired
    private ISubjectScoreService subjectScoreService;

    @Autowired
    private StudentMapper studentMapper;

    @Autowired
    private SubjectMapper subjectMapper;

    @Autowired
    private StudentAnswerMapper studentAnswerMapper;

    @Autowired
    private SubjectScoreMapper subjectScoreMapper;

    @Autowired
    private TaskMapper taskMapper;

    @Autowired
    private ItemScoreMapper itemScoreMapper;

    @Autowired
    private ProjectMapper projectMapper;

    @Autowired
    private IProjectDataDownLoadService projectDataDownLoadService;

    @Resource
    TpsClient tpsClient;

    @Resource
    GlobalConfig globalConfig;

    @Autowired
    private IProjectDataDownLoadRecordService projectDataDownLoadRecordService;

    @Autowired
    private IStudentSyncRecodeService studentSyncRecodeService;

    @Autowired
    private ExamExpertMapper examExpertMapper;

    @Autowired
    private ProjectSettingMapper projectSettingMapper;

    @Autowired
    private PieceScoreMapper pieceScoreMapper;

    @Autowired
    private PaperItemMapper paperItemMapper;

    @Autowired
    private TaskTimeMapper timeMapperask;

    /**
     * 从考务获取已完成的考试任务，并从数据库中统计对应任务的评卷数量信息
     * 若数据库中没有该考试任务，则表示该任务尚未初始化
     * 比较评卷总量和已完成的评卷量，判断该考试任务是评卷中还是已完成
     *
     * @param projectName 项目名称，供查询
     * @param current     分页的当前页码
     * @param size        分页大小，默认为6
     * @return 返回考试任务分页数据
     */
    @Override
    public PageParam<ProjectVo> getPageProject(String projectName, String markStatus, long current, long size) {
        // 从考务读取已完成考试任务列表
        ApiResponse<PageParam<ProjectVo>> res = kwClient.getAllProject(projectName, markStatus, current, size);
        if (null != res && null != res.getData() && CollectionUtils.isEmpty(res.getData().getRecords())) {
            List<ProjectVo> projectVos = res.getData().getRecords();
            // 遍历考试任务，判断任务状态
            for (ProjectVo projectVo : projectVos) {
                String projectId = projectVo.getId();
                Project project = getById(projectId);
                // 判断任务是否已经初始化
                if (project == null) {
                    projectVo.setMarkStatus(0);
                    projectVo.setCompleteNum(0);
                    projectVo.setTotalNum(0);
                    continue;
                }
                // 对已经初始化任务统计其任务总量和已完成评卷量
                Integer studentNum = studentService.getStudentNum(projectId, null);
                // 在整卷评卷模式下，评卷任务总量即为考生数量，已评总量就是具有主观分数的考生数量，在分块评卷模式下这个两个数量需要重新定义
                Integer markNum = subjectScoreService.getMarkNum(projectId, null);
                studentNum = studentNum == null ? 0 : studentNum;
                markNum = markNum == null ? 0 : markNum;
                // 判断考试任务是评卷中还是已完成
                if (markNum < studentNum) {
                    projectVo.setMarkStatus(1); //未评完
                } else {
                    projectVo.setMarkStatus(2); //已评完
                }
                projectVo.setTotalNum(studentNum);
                projectVo.setCompleteNum(markNum);
            }
        }
        //projectVos.sort((o1, o2) -> o1.getMarkStatus().compareTo(o2.getMarkStatus()));
        return res.getData();
    }


    /**
     * 初始化评卷项目
     *
     * @param projectId 评卷任务id
     * @return 返回状态码
     * @throws Exception 发生异常则rollback
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    // @GlobalTransactional(rollbackFor = {MarkException.class, Exception.class})
    public void initProject(String projectId, String userId) throws MarkException, Exception {
        //1.保存评卷项目信息
        this.saveProject(projectId, userId);

        //2.初始化评卷参数
        configService.initMarkParam(projectId);

        //3.考务获取科目信息并写入数据库
        subjectService.initSubject(projectId);

        //4.调用TkClient获取试卷与试题信息并写入数据库
        paperService.initPaper(projectId, null);

        //5.调用BmClient 获取考生信息并写入数据库
        List<StudentExtendDTO> studentDTOs = studentService.initStudent(projectId);

        //6.调用KwClient获取考生答案并写入数据库
        if (CollectionUtils.isNotEmpty(studentDTOs)) {
            for (StudentExtendDTO studentExtendDTO : studentDTOs) {
                studentAnswerService.initStudentAnswer(projectId, studentExtendDTO.getAdmissionNum());
            }
        }

        //7. 初始化客观分数
        subjectScoreService.initKgScore(projectId, null);

        //8.初始化评卷块
        initMarkConstruct(projectId, userId, null, studentDTOs);

        List<String> admissions = studentDTOs.stream().map(item -> item.getAdmissionNum()).distinct().collect(Collectors.toList());
        MarkCandidateParam params = new MarkCandidateParam();
        params.setProjectId(projectId);
        params.setAdmissionNumbers(admissions);
        ApiResponse<?> response = kwClient.markCandidate(params);
        if (!ResponseCode.SUCCESS.equals(response.getCode())) {
            logger.error("考务标记考生失败{},{}", projectId, JSON.toJSON(admissions));
            throw new MarkException(ResponseCode.KW_MARKSTUDENT_FAIL.toString());
        }

    }


    /**
     * 初始化评卷项目
     *
     * @param projectId 评卷任务id
     * @param userId    当前用户id
     * @return 返回状态码
     * @throws Exception 发生异常则rollback
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void initProjectForTask(String projectId, String officeId) throws MarkException, Exception {
        //1.保存评卷项目信息
        this.saveProject(projectId, officeId);

        //2.初始化评卷参数
        configService.initMarkParam(projectId);

        //3.考务获取科目信息并写入数据库
        subjectService.initSubject(projectId);

        //4.调用kw获取试卷与试题信息并写入数据库
        paperService.initPaper(projectId, null);

        //5.调用kw获取考生信息并写入数据库
        List<StudentExtendDTO> studentDTOs = studentService.initStudent(projectId);

        //6.调用KwClient获取考生答案并写入数据库
        if (CollectionUtils.isEmpty(studentDTOs)) {
            throw new MarkException("未获取到考试项目下考生信息");
        }

        Set<String> admissionNums = studentDTOs.stream().filter(s -> StringUtils.isNotEmpty(s.getAdmissionNum())).map(s -> s.getAdmissionNum()).collect(Collectors.toSet());
        if (CollectionUtils.isNotEmpty(admissionNums)) {
            for (String admissionNum : admissionNums) {
                studentAnswerService.initStudentAnswer(projectId, admissionNum);
            }
        }

        //7. 初始化客观分数
        subjectScoreService.initKgScore(projectId, null);

        //8.初始化评卷块
        initMarkConstruct(projectId, officeId, null, studentDTOs);

        //9. 删除没有主观题的所有同步信息
        deleteNotSubjectiveSubject(projectId);

        List<String> admissions = studentDTOs.stream().map(item -> item.getAdmissionNum()).distinct().collect(Collectors.toList());
        List<List<String>> partitionAdmissions = Lists.partition(admissions, globalConfig.getBatchExamineeMarkSize());
        // 如果数据量比较大的话，分组修改1000一组
        if (CollectionUtils.isNotEmpty(partitionAdmissions)) {
            for (List<String> admissionsList : partitionAdmissions) {
                MarkCandidateParam params = new MarkCandidateParam();
                params.setProjectId(projectId);
                params.setAdmissionNumbers(admissionsList);
                ApiResponse<?> response = kwClient.markCandidate(params);
                if (null != response && null != response.getCode() && !ResponseCode.SUCCESS.equals(response.getCode())) {
                    throw new MarkException("考务标记考生失败");
                }
            }
        }
    }


    /**
     * 增量任务初始化
     *
     * @param projectId 评卷任务id
     * @param userId    用户id
     * @return 返回状态码
     * @throws Exception 异常
     */
    @Override
    public void addProjectStudent(String projectId, String userId) throws Exception {
        // 更新项目信息
        updateProject(projectId);

        ApiResponse<?> res = kwClient.getIncrementCandidate(projectId);
        logger.info("## 拉取到的增量考生数据projectId:{},result:{}", projectId, JSONObject.toJSONString(res));
        String admissionString = (String) res.getData();
        if (StringUtils.isEmpty(admissionString)) {
            return;
        }
        // 1、新增项目考生信息
        List<String> admissionList = Arrays.asList(admissionString.split(","));
        if (CollectionUtils.isNotEmpty(admissionList)) {
            for (String admissionNum : admissionList) {
                // 1.1初始化科目
                subjectService.initSubject(projectId);
                // 1.2初始化试卷
                paperService.initPaper(projectId, admissionNum);
                // 1.3新增考生信息
                List<StudentExtendDTO> studentDTOs = studentService.incrementStudent(projectId, admissionNum);
                // 1.4新增考生答案信息
                studentAnswerService.initStudentAnswer(projectId, admissionNum);
                // 1.5获取客观题成绩
                subjectScoreService.initKgScore(projectId, admissionNum);
                // 1.6初始化评卷块
                initMarkConstruct(projectId, userId, admissionNum, studentDTOs);
            }
        }
        // 2、标记考生
        MarkCandidateParam params = new MarkCandidateParam();
        params.setProjectId(projectId);
        params.setAdmissionNumbers(admissionList);
        ApiResponse<?> response = kwClient.markCandidate(params);
        if (null != response && null != response.getCode() && !ResponseCode.SUCCESS.equals(response.getCode())) {
            logger.error("考务标记考生失败{},{}", projectId, JSON.toJSON(admissionList));
            throw new MarkException(ResponseCode.INIT_PROJECT_NO_STUDENT_ANSWER.toString());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer addProjectStudentForTask(String projectId, String userId) throws MarkException, Exception {
        // 更新项目信息
        updateProject(projectId);

        ApiResponse<?> res = kwClient.getIncrementCandidate(projectId);
        logger.info("## 拉取到的增量考生数据projectId:{},result:{}", projectId, JSONObject.toJSONString(res));
        String admissionString = (String) res.getData();
        if (StringUtils.isEmpty(admissionString)) {
            return ResponseCode.SUCCESS;
        }

        // 1、新增项目考生信息
        List<String> admissionList = Arrays.asList(admissionString.split(","));
        if (CollectionUtils.isNotEmpty(admissionList)) {
            for (String admissionNum : admissionList) {
                // 1.1初始化科目
                subjectService.initSubject(projectId);
                // 1.2初始化试卷
                paperService.initPaper(projectId, admissionNum);
                // 1.3新增考生信息
                List<StudentExtendDTO> studentDTOs = studentService.incrementStudent(projectId, admissionNum);
                // 1.4新增考生答案信息
                studentAnswerService.initStudentAnswer(projectId, admissionNum);
                // 1.5获取客观题成绩
                subjectScoreService.initKgScore(projectId, admissionNum);
                // 1.6初始化评卷块
                initMarkConstruct(projectId, userId, admissionNum, studentDTOs);
            }
        }

        //2. 删除没有主观题的所有同步信息
        deleteNotSubjectiveSubject(projectId);

        MarkCandidateParam params = new MarkCandidateParam();
        params.setProjectId(projectId);
        params.setAdmissionNumbers(admissionList);
        // 2、标记考生
        ApiResponse<?> response = kwClient.markCandidate(params);
        if (!ResponseCode.SUCCESS.equals(response.getCode())) {
            throw new Exception("考务标记考生失败");
        }
        return ResponseCode.SUCCESS;
    }


    // @GlobalTransactional(rollbackFor = {MarkException.class, Exception.class})
    private void incrementStudentAndAnswer(String projectId, String userId, String admissionNum) throws Exception {
        List<Subject> subjects = subjectService.selectByProjectId(projectId);
        // 1.根据科目初始化考生试卷【这一块不要】
        Integer code = paperService.initPaperBySubjets(projectId, admissionNum, subjects);
        if (code == ResponseCode.INIT_PROJECT_NO_PAPER) {
            throw new MarkException(ResponseCode.INIT_PROJECT_NO_PAPER.toString());
        }
        // 2.获取考生信息并写入数据库
        List<StudentExtendDTO> students = studentService.incrementStudent(projectId, admissionNum);
        if (CollectionUtils.isEmpty(students)) {
            logger.error("## 项目考生查询不到考生信息:{},{}", projectId, admissionNum);
            throw new MarkException(ResponseCode.INIT_PROJECT_NO_STUDENT.toString());
        }
        // 3.调用KwClient获取考生答案并写入数据库
        List<StudentAnswerVo> studentAnswers = studentAnswerService.initStudentAnswer(projectId, admissionNum);
        if (CollectionUtils.isEmpty(studentAnswers)) {
            logger.error("## 项目考生查询不到考生作答信息:{},{}", projectId, admissionNum);
            throw new MarkException(ResponseCode.INIT_PROJECT_NO_STUDENT_ANSWER.toString());
        }
        // 4.初始化考生客观题成绩
        subjectScoreService.initKgScore(projectId, admissionNum);
        // 5.调用initMarkConstruct初始化评卷结构
        initMarkConstruct(projectId, userId, admissionNum, students);
        // 标记考生
        List<String> admissionList = new ArrayList<>();
        admissionList.add(admissionNum);
        MarkCandidateParam params = new MarkCandidateParam();
        params.setProjectId(projectId);
        params.setAdmissionNumbers(admissionList);
        ApiResponse<?> response = kwClient.markCandidate(params);

        if (null == response) {
            logger.info("## 考务标记考生获取返回异常-{}", admissionList);
            // 如果同步失败，需要记录同步信息
            StudentSyncRecode studentSyncRecode = new StudentSyncRecode();
            studentSyncRecode.setProjectId(projectId);
            studentSyncRecode.setStudentId(admissionNum);
            studentSyncRecode.setExamId(projectId);
            studentSyncRecode.setSyncData(admissionNum);
            studentSyncRecode.setSyncType("update");
            studentSyncRecode.setSyncInteface("kwClient.markCandidate");
            studentSyncRecode.setSyncStatus("1");
            studentSyncRecodeService.save(studentSyncRecode);
            throw new Exception("考务标记考生异常");
        }
        if (null != response && !ResponseCode.KW_MARK_STUDENT_STATUS_SUCCESS.equals(response.getCode())) {
            logger.info("## 考务标记考生获取返回失败-{}", admissionList);
            throw new Exception("考务标记考生失败");
        }
    }

    /**
     * 保存评卷项目信息
     *
     * @param projectId
     */
    private void saveProject(String projectId, String officeId) {
        // 1.初始化项目
        if (projectId == null) {
            throw new MarkException(ResponseCode.INIT_PROJECTID_NULL.toString());
        }
        Project project = this.getById(projectId);
        if (project != null && project.getIsInit()) {
            // 1.1已經初始化過了
            throw new MarkException(ResponseCode.INIT_PROJECT_EXIST_ERROR.toString());
        }
        // 1.2.调用KwClient获取项目相关信息，并插入数据库
        ApiResponse<Project> res = kwClient.getProjectById(projectId);
        logger.info("## 拉取到的评卷项目数据:{}", JSONObject.toJSONString(res));
        if (null != res) {
            project = res.getData();
        }
        logger.info("## 拉取到的评卷项目数据-2:{}", JSONObject.toJSONString(project));
        if (project != null) {
            project.setIsInit(true);
            project.setOfficeId(officeId);
            project.setVersion(CommonEnums.PROJECT_VERSION_1.getCode());
            // 如果是无考场数据
            if (CommonEnums.EXAM_CLASSFICATION_0.getCode().equals(project.getExamClassfication())) {
                project.setTaskName((project.getProjectName()));
                project.setTaskId(project.getId());
                project.setTaskBeginDate(project.getBeginDate());
                project.setTaskEndDate(project.getEndDate());
            }
            this.save(project);
        } else {
            throw new MarkException(ResponseCode.INIT_PROJECT_NOT_EXIST.toString());
        }
    }

    /**
     * 更新项目信息
     *
     * @param projectId
     */
    private void updateProject(String projectId) {
        // 拉取最新的项目信息
        ApiResponse<Project> res = kwClient.getProjectById(projectId);
        logger.info("## 拉取到的评卷项目数据:{}", JSONObject.toJSONString(res));
        // 更新
        Project project = this.baseMapper.selectById(projectId);
        Project newProject = res.getData();
        if (null != newProject && null != project) {
            BeanUtils.copyProperties(newProject, project);
            if (CommonEnums.EXAM_CLASSFICATION_0.getCode().equals(project.getExamClassfication())) {
                project.setProjectName(newProject.getProjectName());
                project.setTaskId(newProject.getId());
                project.setTaskName(newProject.getProjectName());
                project.setTaskBeginDate(newProject.getBeginDate());
                project.setTaskEndDate(newProject.getEndDate());
            }
            this.baseMapper.updateById(project);
        }
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void repealCandidateMark(String projectId, String admissionNums) throws Exception {
        logger.info("## 撤销交卷操作-projectId:{},admissionNums:{}", projectId, admissionNums);

        //检查评卷项目是否已经初始化，未初始化的话直接视为撤销考生评卷成功，无需做接下来的操作
        Project project = this.getById(projectId);
        if (project == null || (!project.getIsInit())) {
            return;
        }
        //admissionNums字符串转为List
        List<String> admissionNumList = Arrays.asList(admissionNums.split(","));
        //查询需要删除的评卷任务id
        List<String> needRemoveTaskIdList = taskMapper.getNeedRemoveTask(projectId, admissionNumList);
        //若评卷项目已经初始化
        if (CollectionUtils.isNotEmpty(needRemoveTaskIdList)) {
            //1、减少科目表中的实考人数(pj_subject)
            this.reduceExamNum(projectId, admissionNumList);
            //2、删除考生表中对应需要撤销评卷的考生(pj_student)
            this.deleteStudent(projectId, admissionNumList);
            //3、考生答案表中删除对应需要撤销评卷的考生答案(pj_student_answer)
            this.deleteStudentAnswer(projectId, admissionNumList);
            //4、考生客观分表中删除对应需要撤销评卷的考生客观分(pj_subject_score)
            this.deleteSubjectScore(projectId, admissionNumList);
            //5、通过id删除评卷任务(pj_task)
            this.removeTask(needRemoveTaskIdList);
            //6、查询需要被删除的试题id
            List<String> itemIdList = itemScoreMapper.selectItemId(admissionNumList, needRemoveTaskIdList);
            //7、删除考生小题分数(pj_item_score)
            this.removeItemScore(admissionNumList, needRemoveTaskIdList, itemIdList);
            //8、删除pieceScore
            this.removePieceScore(admissionNumList, needRemoveTaskIdList);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Page<ProjectVo> page(Page<ProjectVo> pageParam, String projectName, String markStatus, String officeId) {
        Page<ProjectVo> page = projectMapper.page(pageParam, projectName, markStatus, officeId);
        if (null == page || CollectionUtils.isEmpty(page.getRecords())) {
            return page;
        }
        //客观题成绩
        Set<String> projectIds = page.getRecords().stream().map(p -> p.getId()).collect(Collectors.toSet());
        LambdaQueryWrapper<SubjectScore> subjectScoreQueryWrapper = new LambdaQueryWrapper<>();
        subjectScoreQueryWrapper.in(SubjectScore::getProjectId, projectIds);
        List<SubjectScore> subjectScores = subjectScoreMapper.selectList(subjectScoreQueryWrapper);
        Map<String, List<SubjectScore>> subjectScoreMap = subjectScores.stream().collect(Collectors.groupingBy(SubjectScore::getProjectId));


        List<ProjectDataDownLoadRecord> projectDataDownLoadZipRecords = projectDataDownLoadRecordService.getIsRunningDownloadZip(projectIds, ProjectDownLoadFileTypeEnum.project_all_zip.getType());
        Map<String, ProjectDataDownLoadRecord> runningDownLoadZipRecordsMap = new HashMap<String, ProjectDataDownLoadRecord>();
        if (!CollectionUtils.isEmpty(projectDataDownLoadZipRecords)) {
            runningDownLoadZipRecordsMap = projectDataDownLoadZipRecords.stream().collect(Collectors.toMap(s -> (s.getProjectId()), projectDataDownLoadRecord -> projectDataDownLoadRecord, (key1, key2) -> key2));
        }
        List<ProjectDataDownLoadRecord> projectDataDownLoadObscereRecords = projectDataDownLoadRecordService.getIsRunningDownloadZip(projectIds, ProjectDownLoadFileTypeEnum.project_objective_score.getType());
        Map<String, ProjectDataDownLoadRecord> runningDownLoadObScoreRecordsMap = new HashMap<String, ProjectDataDownLoadRecord>();
        if (!CollectionUtils.isEmpty(projectDataDownLoadObscereRecords)) {
            runningDownLoadZipRecordsMap = projectDataDownLoadObscereRecords.stream().collect(Collectors.toMap(s -> (s.getProjectId()), projectDataDownLoadRecord -> projectDataDownLoadRecord, (key1, key2) -> key2));
        }
        // 获取项目下的所有客观题列表
        Map<String, List<PaperItem>> projectObjectiveItemMap = paperItemService.selectAllObjectiveItemsByProjectIds(new ArrayList<String>(projectIds));

        for (ProjectVo projectVo : page.getRecords()) {
            projectVo.setShowObjScoreButton(1);
            if (!MapUtils.isEmpty(subjectScoreMap) && null != subjectScoreMap.get(projectVo.getId())) {
                Set<BigDecimal> scores = subjectScoreMap.get(projectVo.getId()).stream().filter(
                        s -> null != s.getObjectiveScore()).map(s -> s.getObjectiveScore()).collect(Collectors.toSet());
                if (CollectionUtils.isEmpty(scores)) {
                    projectVo.setShowObjScoreButton(0);
                }
            }

            ProjectDataDownLoadRecord runningZipRecord = runningDownLoadZipRecordsMap.get(projectVo.getId());
            if (null != runningZipRecord) {
                projectVo.setRunningDownLoadZipTask(Boolean.TRUE);
            }
            ProjectDataDownLoadRecord runningObScoreRecord = runningDownLoadObScoreRecordsMap.get(projectVo.getId());
            if (null != runningObScoreRecord) {
                projectVo.setRunningDownLoadObScoreTask(Boolean.TRUE);
            }
            // 是否有客观题
            List<PaperItem> objectiveItems = projectObjectiveItemMap.get(projectVo.getId());
            if (CollectionUtils.isEmpty(objectiveItems)) {
                projectVo.setHasObjectiveItems(Boolean.FALSE);
            }
        }
        return page;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProjectInfo(ProjectInfoParam projectInfoParam) throws Exception {
        Project project = this.getById(projectInfoParam.getExamId());
        if (project != null && project.getIsInit()) {
            //修改评卷项目名称、考试起始时间
            project.setProjectName(projectInfoParam.getExamName())
                    .setBeginDate(projectInfoParam.getBeginDate())
                    .setEndDate(projectInfoParam.getEndDate());
            if (!this.updateById(project)) {
                throw new Exception("更新评卷项目信息失败");
            }
        }
    }


    /**
     * 从考务获取已完成的考试任务，并从数据库中统计对应任务的评卷数量信息
     * 若数据库中没有该考试任务，则表示该任务尚未初始化
     * 比较评卷总量和已完成的评卷量，判断该考试任务是评卷中还是已完成
     *
     * @param projectName 项目名称，供查询
     * @return 返回考试任务分页数据
     */
    @Override
    public IPage<ProjectVo> getPageProjectList(PageParam<ProjectVo> pageParam, String currUserId, String projectName, String examState, String mobileMark) {
        int mobileMarkflag = Integer.parseInt(mobileMark);
        Date currDate = new Date();
        IPage<ProjectVo> page = projectMapper.selectNoRoomPageProjectList(pageParam, currUserId, projectName, examState, mobileMarkflag, currDate);
        if (null == page || CollectionUtils.isEmpty(page.getRecords())) {
            return page;
        }
        List<ProjectVo> projectVos = page.getRecords();


        Set<String> projectIds = projectVos.stream().map(e -> e.getId()).collect(Collectors.toSet());

        //评卷设置
        LambdaQueryWrapper<ProjectSetting> projectSettingWrapper = new LambdaQueryWrapper<>();
        projectSettingWrapper.in(ProjectSetting::getProjectId, projectIds);
        List<ProjectSetting> projectSettings = projectSettingMapper.selectList(projectSettingWrapper);
        Map<String, String> settingMap = projectSettings.stream().collect(Collectors.toMap(t -> t.getProjectId(), t -> t.getMarkModel(), (key1, key2) -> key2));

        //全部任务
        LambdaQueryWrapper<Task> taskWrapper = new LambdaQueryWrapper<>();
        taskWrapper.in(Task::getProjectId, projectIds);
        List<Task> tasks = taskMapper.selectList(taskWrapper);

        //全部任务Id
        Set<String> subjectIds = tasks.stream().map(e -> e.getSubjectId()).collect(Collectors.toSet());
        QueryWrapper<Subject> subjectWrapper = new QueryWrapper<>();
        subjectWrapper.lambda().in(Subject::getSubjectId, subjectIds);

        //全部科目名称
        List<Subject> subjects = subjectMapper.selectList(subjectWrapper);
        Map<String, String> subjectMap = subjects.stream().collect(Collectors.toMap(t -> t.getSubjectId(), t -> t.getSubjectName(), (key1, key2) -> key2));

        //当前用户全部的评卷人数
        Set<String> poeceIds = tasks.stream().map(e -> e.getPieceId()).collect(Collectors.toSet());
        QueryWrapper<PieceScore> pieceScoreWrapper = new QueryWrapper<>();
        pieceScoreWrapper.lambda().eq(PieceScore::getUserId, currUserId)
                .in(poeceIds.size() > 0, PieceScore::getPieceId, poeceIds);
        List<PieceScore> pieceScores = pieceScoreMapper.selectList(pieceScoreWrapper);
//        Map<String,List<PieceScore>> pieceScoreMap = pieceScores.stream().collect(Collectors.groupingBy(c -> c.getPieceId()));
        //登录人下的任务
        Set<String> userTaskIds = pieceScores.stream().map(p -> p.getTaskId()).collect(Collectors.toSet());
        Iterator<Task> iterator = tasks.iterator();
        while (iterator.hasNext()) {
            Task task = iterator.next();
            if (!userTaskIds.contains(task.getId())) {
                iterator.remove();
            }
        }
        //处理评卷设置和评卷内容的拼接
        for (ProjectVo project : projectVos) {
            //拼接评卷内容
            StringBuilder setSettingStr = new StringBuilder();
            String markModelDesc = MarkmodelEnums.getMarkmodelEnumByCode(project.getMarkModel()).getDesc();
            String markTypeDesc = MarkTypeEnums.getMarkTypeEnumsByCode(project.getMarkType()).getDesc();
            String rejudgeDesc = RejudgeEnums.getrejudgeEnumsByCode(project.getRejudge()).getDesc();
            appendStr(setSettingStr, markModelDesc);
            appendStr(setSettingStr, markTypeDesc);
            appendStr(setSettingStr, rejudgeDesc);
            if (setSettingStr.length() > 1) {
                setSettingStr = setSettingStr.deleteCharAt(setSettingStr.length() - 1);
            }
            project.setSettingStr(setSettingStr.toString());


            //统计任务id
            List<String> taskIds = new ArrayList<>();
            if (MapUtils.isNotEmpty(settingMap) && MarkmodelEnums.MARK_MODEL_SUBJECT.getCode().equals(settingMap.get(project.getId()))) {

                //科目分组下所以的任务（含不同考场）
                Map<String, List<Task>> taskListMap = tasks.stream().filter(e -> !StringUtils.isEmpty(e.getSubjectId()))
                        .collect(Collectors.groupingBy(t -> t.getProjectId() + t.getSubjectId()));

                String key = project.getId() + project.getSubjectId();
                if (MapUtils.isNotEmpty(taskListMap) && !CollectionUtils.isEmpty(taskListMap.get(key))) {
                    taskIds = taskListMap.get(key).stream().map(e -> e.getId()).collect(Collectors.toList());
                    project.setSubjectId(taskListMap.get(key).get(0).getSubjectId());
                }
                project.setModeStr(buildModeStr(key, taskListMap, subjectMap));
            } else if (MapUtils.isNotEmpty(settingMap) && MarkmodelEnums.MARK_MODEL_BATCH.getCode().equals(settingMap.get(project.getId()))) {
                //批次分组下所以的任务（含不同考场，不同科目）
                String key = project.getId() + project.getBatchId();
                Map<String, List<Task>> taskListMap = tasks.stream().filter(e -> !StringUtils.isEmpty(e.getBatchId()))
                        .collect(Collectors.groupingBy(t -> t.getProjectId() + t.getBatchId()));
                if (MapUtils.isNotEmpty(taskListMap) && !CollectionUtils.isEmpty(taskListMap.get(key))) {
                    taskIds = taskListMap.get(key).stream().map(e -> e.getId()).collect(Collectors.toList());
                    project.setSubjectId(taskListMap.get(key).get(0).getSubjectId());
                }
                project.setModeStr(buildModeStr(key, taskListMap, subjectMap));

            } else if (MapUtils.isNotEmpty(settingMap) && MarkmodelEnums.MARK_MODEL_ROOM.getCode().equals(settingMap.get(project.getId()))) {
                //考场分组下所以的任务（含不同科目）
                Map<String, List<Task>> taskListMap = tasks.stream().filter(e -> !StringUtils.isEmpty(e.getRoomId()))
                        .collect(Collectors.groupingBy(t -> t.getProjectId() + t.getRoomId()));
                String key = project.getId() + project.getRoomId();
                if (MapUtils.isNotEmpty(taskListMap) && !CollectionUtils.isEmpty(taskListMap.get(key))) {
                    taskIds = taskListMap.get(key).stream().map(e -> e.getId()).collect(Collectors.toList());
                    project.setSubjectId(taskListMap.get(key).get(0).getSubjectId());
                }

                project.setModeStr(buildModeStr(key, taskListMap, subjectMap));

                // 给按考场评卷的情况补一个评卷开始结束时间
                if (null == project.getPjBeginDate() && null == project.getPjEndDate()) {
                    List<ProjectVo> collect = projectVos.stream()
                            .filter(item -> project.getId().equals(item.getId()))
                            .filter(item -> null != item.getPjBeginDate() || null != item.getPjEndDate()).collect(Collectors.toList());
                    if (CollectionUtil.isNotEmpty(collect) && collect.size() > 0) {
                        project.setPjBeginDate(collect.get(0).getPjBeginDate());
                        project.setPjEndDate(collect.get(0).getPjEndDate());
                    }
                }
            }

            //应评人数
            project.setTotalNum(0);
            //已完成人数
            project.setCompleteNum(0);
            if (CollectionUtils.isNotEmpty(pieceScores) && CollectionUtils.isNotEmpty(taskIds)) {
                int allCount = 0;
                int finishCount = 0;
//                for (Map.Entry<String, List<PieceScore>> entry : pieceScoreMap.entrySet()) {
                for (PieceScore pieceScore : pieceScores) {
                    if (taskIds.contains(pieceScore.getTaskId())) {
                        allCount++;
                    }
                    if (taskIds.contains(pieceScore.getTaskId()) &&
                            CommonEnums.PIECCE_SCORE_FINSH_1.getCode().equals(pieceScore.getFinish())) {
                        finishCount++;
                    }
                }
//                }
                project.setTotalNum(allCount);
                project.setCompleteNum(finishCount);
            }

            //处理未评人数
            project.setUnmarkNum(project.getTotalNum() - project.getCompleteNum());

            //虚拟考场，考试名称为task_name
            if (CommonEnums.EXAM_CLASSFICATION_1.getCode().equals(project.getExamClassfication())
                    || CommonEnums.EXAM_CLASSFICATION_2.getCode().equals(project.getExamClassfication())) {
                project.setProjectName(project.getTaskName());
            }
        }

        return page;
    }


    /**
     * 封装评卷内容
     *
     * @param key
     * @param taskListMap
     * @param subjectMap
     * @return
     */
    private String buildModeStr(String key, Map<String, List<Task>> taskListMap, Map<String, String> subjectMap) {
        //拼接评卷设置
        StringBuilder setModeStr = new StringBuilder();
        Set<String> retSubjecNames = new HashSet<>();
        Set<String> retBatchNames = new HashSet<>();
        Set<String> retPlaceNames = new HashSet<>();
        Set<String> retRoomNames = new HashSet<>();
        if (MapUtils.isNotEmpty(taskListMap) && !CollectionUtils.isEmpty(taskListMap.get(key))) {
            retBatchNames = taskListMap.get(key).stream().map(e -> e.getBatchName()).collect(Collectors.toSet());
            retPlaceNames = taskListMap.get(key).stream().map(e -> e.getPlaceName()).collect(Collectors.toSet());
            retRoomNames = taskListMap.get(key).stream().map(e -> e.getRoomName()).collect(Collectors.toSet());
            for (Task t : taskListMap.get(key)) {
                if (MapUtils.isEmpty(subjectMap) || StringUtils.isEmpty(subjectMap.get(t.getSubjectId()))) {
                    continue;
                }
                retSubjecNames.add(subjectMap.get(t.getSubjectId()));
            }
        }
        if (CollectionUtils.isNotEmpty(retSubjecNames)) {
            appendStr(setModeStr, StringUtils.join(retSubjecNames, "/"));
        }
        if (CollectionUtils.isNotEmpty(retBatchNames)) {
            appendStr(setModeStr, StringUtils.join(retBatchNames, "/"));
        }
        if (CollectionUtils.isNotEmpty(retPlaceNames)) {
            appendStr(setModeStr, StringUtils.join(retPlaceNames, "/"));
        }
        if (CollectionUtils.isNotEmpty(retRoomNames)) {
            appendStr(setModeStr, StringUtils.join(retRoomNames, "/"));
        }
        if (setModeStr.length() > 1) {
            setModeStr = setModeStr.deleteCharAt(setModeStr.length() - 1);
        }

        return setModeStr.toString();
    }


    /**
     * 拼接stringbuild
     *
     * @param sb
     * @param str
     */
    private void appendStr(StringBuilder sb, String str) {
        if (!StringUtils.isEmpty(str)) {
            sb.append(str + "、");
        }
    }


    @Override
    public ProjectVo getProjectInfo(String currUserId, String projectId, String subjectId, String batchId, String placeId, String roomId) {

        ProjectVo projectVo = new ProjectVo();
        Project project = projectMapper.selectById(projectId);
        if (null == project) {
            return projectVo;
        }
        projectVo.setId(project.getId());
        projectVo.setProjectName(project.getTaskName());
        projectVo.setExamTypeName(project.getExamTypeName());
        projectVo.setExamClassfication(project.getExamClassfication());
        projectVo.setBeginDate(project.getBeginDate());
        projectVo.setEndDate(project.getEndDate());
        LambdaQueryWrapper<ProjectSetting> projectSettingMWrapper = new LambdaQueryWrapper<>();
        projectSettingMWrapper.eq(ProjectSetting::getProjectId, project.getId());
        List<ProjectSetting> projectSettings = projectSettingMapper.selectList(projectSettingMWrapper);
        if (CollectionUtils.isEmpty(projectSettings)) {
            return projectVo;
        }
        projectVo.setSubjectId(subjectId);
        projectVo.setBatchId(batchId);
        projectVo.setPlaceId(placeId);
        projectVo.setRoomId(roomId);
        projectVo.setMarkType(projectSettings.get(0).getMarkType());
        projectVo.setAnonymous(projectSettings.get(0).getAnonymous());
        projectVo.setMarkModel(projectSettings.get(0).getMarkModel());
        projectVo.setRejudge(projectSettings.get(0).getRejudge());

        String strSubjectName = null;
        String strBatchName = null;
        String strRoomName = null;
        String strPlaceName = null;
        List<PieceScore> pieceScores = null;
        List<Task> tasks = null;
        if (MarkmodelEnums.MARK_MODEL_SUBJECT.getCode().equals(projectSettings.get(0).getMarkModel())) {
            //全部任务
            LambdaQueryWrapper<Task> taskWrapper = new LambdaQueryWrapper<>();
            taskWrapper.eq(Task::getProjectId, project.getId()).eq(Task::getSubjectId, subjectId);
            tasks = taskMapper.selectList(taskWrapper);
            if (CollectionUtils.isEmpty(tasks)) {
                return projectVo;
            }
            //全部的评卷任务数
            Set<String> allTaskIds = tasks.stream().map(Task::getId).collect(Collectors.toSet());
            QueryWrapper<PieceScore> pieceScoreWrapper = new QueryWrapper<>();
            pieceScoreWrapper.lambda().eq(PieceScore::getUserId, currUserId)
                    .in(allTaskIds.size() > 0, PieceScore::getTaskId, allTaskIds);
            pieceScores = pieceScoreMapper.selectList(pieceScoreWrapper);
            //过滤当前登录人下的任务
            Set<String> taskIds = pieceScores.stream().map(PieceScore::getTaskId).collect(Collectors.toSet());
            Iterator<Task> iterator = tasks.iterator();
            while (iterator.hasNext()) {
                Task task = iterator.next();
                if (!taskIds.contains(task.getId())) {
                    iterator.remove();
                }
            }
            strSubjectName = getSubjectName(tasks, project.getId());
            strBatchName = getBatchName(tasks);
            Set<String> roomNames = tasks.stream().filter(e -> !StringUtils.isEmpty(e.getRoomName())).map(e -> e.getRoomName()).collect(Collectors.toSet());
            Set<String> placeNames = tasks.stream().filter(e -> !StringUtils.isEmpty(e.getPlaceName())).map(e -> e.getPlaceName()).collect(Collectors.toSet());
            strPlaceName = String.join("-", placeNames);
            strRoomName = String.join("-", roomNames);
            //评卷时间
            LambdaQueryWrapper<TaskTime> taskTimeWrapper = new LambdaQueryWrapper<>();
            taskTimeWrapper.eq(TaskTime::getProjectId, project.getId()).eq(TaskTime::getSubjectId, subjectId);
            List<TaskTime> taskTimes = timeMapperask.selectList(taskTimeWrapper);
            if (!CollectionUtils.isEmpty(taskTimes)) {
                projectVo.setPjBeginDate(taskTimes.get(0).getStartTime());
                projectVo.setPjEndDate(taskTimes.get(0).getEndTime());
            }

        } else if (MarkmodelEnums.MARK_MODEL_BATCH.getCode().equals(projectSettings.get(0).getMarkModel())) {
            LambdaQueryWrapper<Task> taskWrapper = new LambdaQueryWrapper<>();
            taskWrapper.eq(Task::getProjectId, projectId).eq(Task::getBatchId, batchId);
            tasks = taskMapper.selectList(taskWrapper);
            if (CollectionUtils.isEmpty(tasks)) {
                return projectVo;
            }
            //全部的评卷任务数
            Set<String> allTaskIds = tasks.stream().map(Task::getId).collect(Collectors.toSet());
            QueryWrapper<PieceScore> pieceScoreWrapper = new QueryWrapper<>();
            pieceScoreWrapper.lambda().eq(PieceScore::getUserId, currUserId)
                    .in(allTaskIds.size() > 0, PieceScore::getTaskId, allTaskIds);
            pieceScores = pieceScoreMapper.selectList(pieceScoreWrapper);

            LambdaQueryWrapper<TaskTime> taskTimeWrapper = new LambdaQueryWrapper<>();
            taskTimeWrapper.eq(TaskTime::getProjectId, project.getId()).eq(TaskTime::getBatchId, batchId);
            List<TaskTime> taskTimes = timeMapperask.selectList(taskTimeWrapper);
            if (!CollectionUtils.isEmpty(taskTimes)) {
                projectVo.setPjBeginDate(taskTimes.get(0).getStartTime());
                projectVo.setPjEndDate(taskTimes.get(0).getEndTime());
            }
            //过滤当前登录人下的任务
            Set<String> taskIds = pieceScores.stream().map(PieceScore::getTaskId).collect(Collectors.toSet());
            Iterator<Task> iterator = tasks.iterator();
            while (iterator.hasNext()) {
                Task task = iterator.next();
                if (!taskIds.contains(task.getId())) {
                    iterator.remove();
                }
            }
            strSubjectName = getSubjectName(tasks, project.getId());
            strBatchName = getBatchName(tasks);

        } else if (MarkmodelEnums.MARK_MODEL_ROOM.getCode().equals(projectSettings.get(0).getMarkModel())) {
            LambdaQueryWrapper<Task> taskWrapper = new LambdaQueryWrapper<>();
            taskWrapper.eq(Task::getProjectId, project.getId()).eq(Task::getRoomId, roomId);
            tasks = taskMapper.selectList(taskWrapper);
            if (CollectionUtils.isEmpty(tasks)) {
                return projectVo;
            }
            //全部的评卷任务数
            Set<String> allTaskIds = tasks.stream().map(Task::getId).collect(Collectors.toSet());
            QueryWrapper<PieceScore> pieceScoreWrapper = new QueryWrapper<>();
            pieceScoreWrapper.lambda().eq(PieceScore::getUserId, currUserId)
                    .in(allTaskIds.size() > 0, PieceScore::getTaskId, allTaskIds);
            pieceScores = pieceScoreMapper.selectList(pieceScoreWrapper);
            //过滤当前登录人下的任务
            Set<String> taskIds = pieceScores.stream().map(PieceScore::getTaskId).collect(Collectors.toSet());
            Iterator<Task> iterator = tasks.iterator();
            while (iterator.hasNext()) {
                Task task = iterator.next();
                if (!taskIds.contains(task.getId())) {
                    iterator.remove();
                }
            }

            Set<String> roomNames = tasks.stream().filter(e -> !StringUtils.isEmpty(e.getRoomName())).map(e -> e.getRoomName()).collect(Collectors.toSet());
            Set<String> placeNames = tasks.stream().filter(e -> !StringUtils.isEmpty(e.getPlaceName())).map(e -> e.getPlaceName()).collect(Collectors.toSet());
            strPlaceName = String.join("-", placeNames);
            strRoomName = String.join("-", roomNames);

            LambdaQueryWrapper<TaskTime> taskTimeWrapper = new LambdaQueryWrapper<>();
            taskTimeWrapper.eq(TaskTime::getProjectId, project.getId()).eq(TaskTime::getBatchId, batchId);
            List<TaskTime> taskTimes = timeMapperask.selectList(taskTimeWrapper);
            if (!CollectionUtils.isEmpty(taskTimes)) {
                projectVo.setPjBeginDate(taskTimes.get(0).getStartTime());
                projectVo.setPjEndDate(taskTimes.get(0).getEndTime());
            }
            strBatchName = getBatchName(tasks);
            strSubjectName = getSubjectName(tasks, project.getId());
        }

        projectVo.setSubjectName(strSubjectName);
        projectVo.setBatchName(strBatchName);
        projectVo.setRoomName(strRoomName);
        projectVo.setPlaceName(strPlaceName);


        //处理未评人数
        projectVo.setCompleteNum(0);
        projectVo.setTotalNum(0);
        if (CollectionUtils.isNotEmpty(pieceScores)) {
            int finishCount = 0;
            projectVo.setTotalNum(pieceScores.size());
            for (PieceScore pieceScore : pieceScores) {
                if (CommonEnums.PIECCE_SCORE_FINSH_1.getCode().equals(pieceScore.getFinish())) {
                    finishCount++;
                }
            }
            projectVo.setCompleteNum(finishCount);
        }
        projectVo.setUnmarkNum(projectVo.getTotalNum().intValue() - projectVo.getCompleteNum().intValue());

        //获取主观题总数
        List<String> subjectIds = tasks.stream().map(e -> e.getSubjectId()).collect(Collectors.toList());
        projectVo.setSubjectiveNum(paperItemMapper.getPaperItemSubjectives(projectId, subjectIds).size());

        //如果类型没有
        if (StringUtils.isEmpty(projectVo.getExamTypeName())) {
            projectVo.setExamTypeName(Constants.MARK_OTHER);
        } else {
            projectVo.setExamTypeName(projectVo.getExamTypeName().replaceAll(",", "--"));
        }

        //拼接评卷设置
        StringBuilder setModeStr = new StringBuilder();
        String batchName = projectVo.getBatchName();
        String subjectName = projectVo.getSubjectName();
        String placeName = projectVo.getPlaceName();
        String roomName = projectVo.getRoomName();
        appendStr(setModeStr, batchName);
        appendStr(setModeStr, subjectName);
        appendStr(setModeStr, placeName);
        appendStr(setModeStr, roomName);
        if (setModeStr.length() > 1) {
            setModeStr = setModeStr.deleteCharAt(setModeStr.length() - 1);
        }
        projectVo.setModeStr(setModeStr.toString());

        //拼接评卷内容
        StringBuilder setSettingStr = new StringBuilder();
        String markModelDesc = MarkmodelEnums.getMarkmodelEnumByCode(projectVo.getMarkModel()).getDesc();
        String markTypeDesc = MarkTypeEnums.getMarkTypeEnumsByCode(projectVo.getMarkType()).getDesc();
        String rejudgeDesc = RejudgeEnums.getrejudgeEnumsByCode(projectVo.getRejudge()).getDesc();
        appendStr(setSettingStr, markModelDesc);
        appendStr(setSettingStr, markTypeDesc);
        appendStr(setSettingStr, rejudgeDesc);
        if (setSettingStr.length() > 1) {
            setSettingStr = setSettingStr.deleteCharAt(setSettingStr.length() - 1);
        }
        projectVo.setSettingStr(setSettingStr.toString());

        //如果匿名，设置名字为***


        //设置是否再评卷的时间内
        if (null == projectVo.getPjBeginDate() && null == projectVo.getPjEndDate()) {
            projectVo.setGradingStatus(CommonEnums.GRADING_STATUS_1.getCode().toString());
        } else if (null != projectVo.getPjEndDate() && projectVo.getPjEndDate().compareTo(new Date()) < 0) {
            projectVo.setGradingStatus(CommonEnums.GRADING_STATUS_2.getCode().toString());
        } else if (null != projectVo.getPjBeginDate() && null != projectVo.getPjEndDate() && projectVo.getPjBeginDate().compareTo(new Date()) <= 0 && projectVo.getPjEndDate().compareTo(new Date()) > 0) {
            projectVo.setGradingStatus(CommonEnums.GRADING_STATUS_1.getCode().toString());
        } else if (null != projectVo.getPjBeginDate() && null == projectVo.getPjEndDate() && projectVo.getPjBeginDate().compareTo(new Date()) <= 0) {
            projectVo.setGradingStatus(CommonEnums.GRADING_STATUS_1.getCode().toString());
        } else if (null == projectVo.getPjBeginDate() && null != projectVo.getPjEndDate() && projectVo.getPjEndDate().compareTo(new Date()) > 0) {
            projectVo.setGradingStatus(CommonEnums.GRADING_STATUS_1.getCode().toString());
        } else {
            projectVo.setGradingStatus(CommonEnums.GRADING_STATUS_0.getCode().toString());
        }

        return projectVo;
    }


    private String getBatchName(List<Task> tasks) {

        Set<String> batchNames = tasks.stream().filter(e -> !StringUtils.isEmpty(e.getBatchName())).map(e -> e.getBatchName()).collect(Collectors.toSet());
        return String.join("-", batchNames);
    }

    private String getSubjectName(List<Task> tasks, String projectId) {
        Set<String> subjectIds = tasks.stream().filter(e -> !StringUtils.isEmpty(e.getSubjectId())).map(e -> e.getSubjectId()).collect(Collectors.toSet());
        if (!CollectionUtils.isEmpty(subjectIds)) {
            QueryWrapper<Subject> subjectWrapper = new QueryWrapper<>();
            subjectWrapper.lambda().eq(Subject::getProjectId, projectId).in(Subject::getSubjectId, subjectIds);
            List<Subject> subjects = subjectMapper.selectList(subjectWrapper);
            Set<String> subjectNames = subjects.stream().filter(e -> !StringUtils.isEmpty(e.getSubjectName())).map(e -> e.getSubjectName()).collect(Collectors.toSet());
            return String.join("-", subjectNames);
        }
        return null;
    }

    protected void reduceExamNum(String projectId, List<String> admissionNums) throws Exception {
        //查询每个科目需要减少的实考人数
        List<NeedReduceDTO> needReduceList = studentMapper.getNeedReduce(projectId, admissionNums);
        //更新科目实考人数
        for (NeedReduceDTO needReduce : needReduceList) {
            if (subjectMapper.updateExamNum(projectId, needReduce.getSubjectId(), needReduce.getExamNum()) != 1) {
                throw new Exception("更新科目实考人数失败");
            }
        }
    }

    protected void deleteStudent(String projectId, List<String> admissionNums) throws Exception {
        if (studentMapper.removeStudent(projectId, admissionNums) < 1) {
            throw new Exception("删除对应需要撤销评卷的考生失败");
        }
    }

    protected void deleteStudentAnswer(String projectId, List<String> admissionNums) throws Exception {
        if (!studentAnswerMapper.removeStudentAnswer(projectId, admissionNums)) {
            throw new Exception("删除对应需要撤销评卷的考生答案失败");
        }
    }

    protected void deleteSubjectScore(String projectId, List<String> admissionNums) throws Exception {
        if (subjectScoreMapper.removeSubjectScore(projectId, admissionNums) < 1) {
            throw new Exception("删除对应需要撤销评卷的分数失败");
        }
    }

    protected void removeTask(List<String> needRemoveTaskIdList) throws Exception {
        if (taskMapper.removeTask(needRemoveTaskIdList) < 1) {
            throw new Exception("删除对应需要撤销评卷的评卷任务失败");
        }
    }

    protected void removeItemScore(List<String> admissionNumList, List<String> needRemoveTaskIdList, List<String> itemIdList) throws Exception {
        if (CollectionUtils.isEmpty(itemIdList)) {
            return;
        }
        if (!itemScoreMapper.removeItemScore(admissionNumList, needRemoveTaskIdList, itemIdList)) {
            throw new Exception("删除考生小题分数失败");
        }
    }

    protected void removePieceScore(List<String> admissionNumList, List<String> needRemoveTaskIdList) {
        pieceScoreMapper.removePieceScore(admissionNumList, needRemoveTaskIdList);
    }

    /**
     * 获取当前任务
     *
     * @param userId 用户id
     * @return 返回当前任务
     */
    @Override
    public Project currentProject(String userId) {
        UserConfig userConfig = userConfigService.getSelectProject(userId, Constants.SELECTED_PROJECT_KEY);
        if (userConfig != null) {
            return this.getById(userConfig.getValue());
        }
        return null;
    }

    /**
     * 初始化评卷结构
     *
     * @param projectId 评卷项目id
     * @param officeId  用户id
     * @param admission 准考证号
     */
    private void initMarkConstruct(String projectId, String officeId, String admission, List<StudentExtendDTO> studentExtendDTOS) {
        //1.读取参数查看评卷模式，默认为整卷模式
        Config config = configService.selectByLabel(projectId, Constants.MARK_MODEL);
        // 如果是整卷评卷
        if (config != null && "A".equals(config.getValue())) {
            //整卷评卷
            if (admission == null) {
                markPaperInit(projectId, studentExtendDTOS);
            } else {
                markPaperIncrment(projectId, officeId, admission, studentExtendDTOS);
            }
        } else if (config != null && "I".equals(config.getValue())) {
            // TODO 按题评卷
        }
    }

    /**
     * 增量任务下初始化评卷结构
     *
     * @param projectId 评卷项目id
     * @param officeId  用户id
     * @param admission 准考证号
     */
    private void markPaperIncrment(String projectId, String officeId, String admission, List<StudentExtendDTO> studentExtendDTOS) {
        // 组装task需要的数据
        Map<String, StudentExtendDTO> studentExtendMap = new HashMap<>();
        for (StudentExtendDTO item : studentExtendDTOS) {
            StringBuilder key = new StringBuilder();
            key.append(item.getProjectId());
            key.append(item.getSubjectId());
            key.append(item.getAdmissionNum());
            studentExtendMap.put(key.toString(), item);
        }

        logger.info("## Incrment studentExtendDTOS：{}", JSONObject.toJSONString(studentExtendMap));

        List<Subject> subjects = subjectService.selectByProjectId(projectId);
        //生成piece、pieceItem、task、teacherTaskInfo
        int subjectOrderNum = 0;
        Config scoreStep = configService.selectByLabel(projectId, Constants.MARK_SCORE_STEP);
        Config reMarkLeng = configService.selectByLabel(projectId, Constants.MARK_REMARK_LENGTH);
        Config minSubmitTime = configService.selectByLabel(projectId, Constants.MARK_MIN_SUBMIT_TIME);
        for (Subject subject : subjects) {
            String subjectId = subject.getSubjectId();
            List<Piece> existPieces = pieceService.selectBySubject(subjectId);
            String subjectPieceId = "";
            if (existPieces == null || existPieces.size() == 0) {
                subjectOrderNum++;
                Piece piece = new Piece();
                piece.setProjectId(projectId);
                piece.setSubjectId(subjectId);
                piece.setParentId("0");
                piece.setPieceName(subject.getSubjectName());
                piece.setPieceType(CommonEnums.PIECE_TYPE_1.getCode());//科目评卷块
                piece.setOrderNum(subjectOrderNum);
                piece.setScoreStep(NullHandleUtils.parseBigDecimal(scoreStep));
                pieceService.save(piece);
                subjectPieceId = piece.getId();
            } else {
                for (Piece piece : existPieces) {
                    if (piece.getPieceType() == 1) {
                        subjectPieceId = piece.getId();
                    }
                }
            }

            List<Paper> papers = paperService.selectBySubject(projectId, subjectId);
            int paperPieceOrderNum = 0;
            String lastPackageId = null;
            for (Paper paper : papers) {
                Piece existePaperPiece = pieceService.selectPaperPiece(projectId, subjectId, paper.getId());
                if (existePaperPiece == null) {
                    if (paper.getPackageId() == null || !paper.getPackageId().equals(lastPackageId)) {
                        //同一个试卷包的paperPieceOrderNum相同
                        paperPieceOrderNum++;
                        lastPackageId = paper.getPackageId();
                    }
                    Piece existPiecePaper = pieceService.selectPaperPiece(projectId, subjectId, paper.getId());
                    if (existPiecePaper == null) {
                        if (paper.getPackageId() == null || !paper.getPackageId().equals(lastPackageId)) {
                            //同一个试卷包的paperPieceOrderNum相同
                            paperPieceOrderNum++;
                            lastPackageId = paper.getPackageId();
                        }
                        String paperId = paper.getId();
                        Piece piecePaper = new Piece();
                        piecePaper.setProjectId(projectId);
                        piecePaper.setSubjectId(subjectId);
                        piecePaper.setParentId(subjectPieceId);
                        piecePaper.setPieceName(paper.getPaperName());
                        piecePaper.setPackageId(paper.getPackageId());
                        piecePaper.setSort(paper.getSort());
                        piecePaper.setPaperId(paperId);
                        //试卷评卷块
                        piecePaper.setPieceType(CommonEnums.PIECE_TYPE_2.getCode());
                        piecePaper.setRejudgeLength(NullHandleUtils.parseInt(reMarkLeng));
                        piecePaper.setMinSubmitTime(NullHandleUtils.parseInt(minSubmitTime));
                        piecePaper.setOrderNum(paperPieceOrderNum);
                        piecePaper.setFullScore(paper.getSubjectiveScore());
                        piecePaper.setScoreStep(NullHandleUtils.parseBigDecimal(scoreStep));
                        pieceService.save(piecePaper);
                        List<PaperItem> paperItems = paperItemService.selectByPaperId(paperId);
                        List<PieceItem> pieceItems = new ArrayList<>();
                        for (PaperItem paperItem : paperItems) {
                            PieceItem pieceItem = new PieceItem();
                            pieceItem.setItemId(paperItem.getTopicId());
                            pieceItem.setPieceId(piecePaper.getId());
                            pieceItems.add(pieceItem);
                        }
                        pieceItemService.saveBatch(pieceItems);
                    }
//                    else {
//                        // 更新一下piece表的packageId
//                        existePaperPiece.setPackageVersion(paper.getPackageVersion());
//                        pieceService.updateById(existePaperPiece);
//                    }
                }
                //createTeacherTaskInfo(projectId, subjectId, userId);
                incrementTask(projectId, subjectId, officeId, admission, studentExtendMap);
            }
        }
    }

    /**
     * 全量初始化评卷结构
     *
     * @param projectId 评卷项目id
     */
    private void markPaperInit(String projectId, List<StudentExtendDTO> studentExtendDTOS) {
        // 组装task需要的数据
        Map<String, StudentExtendDTO> studentExtendMap = new HashMap<String, StudentExtendDTO>();
        for (StudentExtendDTO item : studentExtendDTOS) {
            StringBuilder key = new StringBuilder();
            key.append(item.getProjectId());
            key.append(item.getSubjectId());
            key.append(item.getAdmissionNum());
            studentExtendMap.put(key.toString(), item);
        }
        logger.debug("## Init studentExtendDTOS：{}", JSONObject.toJSONString(studentExtendMap));
        //生成piece、pieceItem、task、teacherTaskInfo
        List<Subject> subjects = subjectService.selectByProjectId(projectId);
        int subjectOrderNum = 0;
        Config scoreStep = configService.selectByLabel(projectId, Constants.MARK_SCORE_STEP);
        Config reMarkLeng = configService.selectByLabel(projectId, Constants.MARK_REMARK_LENGTH);
        Config minSubmitTime = configService.selectByLabel(projectId, Constants.MARK_MIN_SUBMIT_TIME);
        for (Subject subject : subjects) {
            String subjectId = subject.getSubjectId();
            subjectOrderNum++;
            Piece piece = new Piece();
            piece.setProjectId(projectId);
            piece.setSubjectId(subjectId);
            piece.setParentId("0");
            piece.setPieceName(subject.getSubjectName());
            piece.setPieceType(CommonEnums.PIECE_TYPE_1.getCode());//科目评卷块
            piece.setOrderNum(subjectOrderNum);
            piece.setScoreStep(NullHandleUtils.parseBigDecimal(scoreStep));
            pieceService.save(piece);
            List<Paper> papers = paperService.selectBySubject(projectId, subjectId);
            int paperPieceOrderNum = 0;
            String lastPackageId = null;
            for (Paper paper : papers) {
                if (paper.getPackageId() == null || !paper.getPackageId().equals(lastPackageId)) {
                    //同一个试卷包的paperPieceOrderNum相同
                    paperPieceOrderNum++;
                    lastPackageId = paper.getPackageId();
                }
                String paperId = paper.getId();
                Piece piecePaper = new Piece();
                piecePaper.setProjectId(projectId);
                piecePaper.setSubjectId(subjectId);
                piecePaper.setParentId(piece.getId());
                piecePaper.setPieceName(paper.getPaperName());
                piecePaper.setPackageId(paper.getPackageId());
                piecePaper.setSort(paper.getSort());
                piecePaper.setPaperId(paperId);
                piecePaper.setPieceType(CommonEnums.PIECE_TYPE_2.getCode());//试卷评卷块
                piecePaper.setRejudgeLength(NullHandleUtils.parseInt(reMarkLeng));
                piecePaper.setMinSubmitTime(NullHandleUtils.parseInt(minSubmitTime));
                piecePaper.setOrderNum(paperPieceOrderNum);
                piecePaper.setFullScore(paper.getSubjectiveScore());
                piecePaper.setScoreStep(NullHandleUtils.parseBigDecimal(scoreStep));
                pieceService.save(piecePaper);
                List<PaperItem> paperItems = paperItemService.selectByPaperId(paperId);
                List<PieceItem> pieceItems = new ArrayList<>();
                for (PaperItem paperItem : paperItems) {
                    PieceItem pieceItem = new PieceItem();
                    pieceItem.setItemId(paperItem.getTopicId());
                    pieceItem.setPieceId(piecePaper.getId());
                    pieceItems.add(pieceItem);
                }
                pieceItemService.saveBatch(pieceItems);
            }
            createTask(projectId, subjectId, studentExtendMap);
        }
    }


    /**
     * 生成教师评卷信息记录
     *
     * @param projectId 评卷项目id
     * @param subjectId 评卷科目id
     * @param userId    用户id
     */
    @Override
    public void createTeacherTaskInfo(String projectId, String subjectId, String userId) {
        List<Piece> pieces = pieceService.selectBySubject(subjectId);
        for (Piece piece : pieces) {
            if (1 == piece.getPieceType()) {
                TeacherTaskInfo teacherTaskInfo = new TeacherTaskInfo();
                teacherTaskInfo.setProjectId(projectId);
                teacherTaskInfo.setSubjectId(subjectId);
                teacherTaskInfo.setPieceCode(piece.getId());
                teacherTaskInfo.setUserId(userId);
                teacherTaskInfo.setTeacherType("0");
                teacherTaskInfo.setStage(Constants.MARK_STAGE);
                teacherTaskInfo.setSumScore(BigDecimal.ZERO);
                teacherTaskInfo.setHighScore(BigDecimal.ZERO);
                teacherTaskInfo.setLowScore(BigDecimal.ZERO);
                teacherTaskInfoService.save(teacherTaskInfo);
            }
        }
    }

    /**
     * 生成评卷任务
     *
     * @param projectId 评卷项目id
     * @param subjectId 评卷科目id
     */
    private void createTask(String projectId, String subjectId, Map<String, StudentExtendDTO> studentExtendMap) {
        // 获取考生，这里需要
        List<Student> students = studentService.getAllStudentByProjectAndSubjectId(projectId, subjectId);
        // 获取评卷块
        List<Piece> pieces = pieceService.selectAllPiecesByProjectIdAndSubject(projectId, subjectId);

        List<Task> tasks = new ArrayList<>();
        for (Student student : students) {
            for (Piece piece : pieces) {
                boolean paperType = CommonEnums.PIECE_TYPE_2.getCode().equals(piece.getPieceType());
                boolean studentPaperIdEqualPiecePaperId = (piece.getPackageId() == null && StringUtils.isNotEmpty(piece.getPaperId()) && StringUtils.isNotEmpty(student.getPaperId()) && piece.getPaperId().equals(student.getPaperId()))
                        || (piece.getPackageId() != null && StringUtils.isNotEmpty(student.getPackageId()) && piece.getPackageId().equals(student.getPackageId()));
                if (paperType && studentPaperIdEqualPiecePaperId) {
                    //考生的PieceId在单体试卷的时候是试卷id，试卷包的时候是试卷包id
                    Task task = new Task();
                    String taskId = UUIDUtils.newSortUUID();
                    task.setId(taskId);
                    task.setProjectId(projectId);
                    task.setSubjectId(subjectId);
                    task.setAdmissionNum(student.getAdmissionNum());
                    task.setEncodeId(student.getAdmissionNum());
                    task.setPieceId(piece.getId());
                    task.setStage(Constants.MARK_STAGE);
                    task.setStatus(CommonEnums.TASK_STATUS_0.getCode());
                    // task 其他数据
                    StringBuilder sb = new StringBuilder();
                    sb.append(projectId);
                    sb.append(subjectId);
                    sb.append(task.getAdmissionNum());
                    StudentExtendDTO studentExtendDTO = studentExtendMap.get(sb.toString());
                    extendTask(task, studentExtendDTO);
                    tasks.add(task);
                }
            }
        }
        taskService.saveBatch(tasks);
    }

    /**
     * 生成增量评卷任务
     *
     * @param projectId    评卷项目id
     * @param subjectId    评卷科目
     * @param officeId     当前用户
     * @param admissionNum 增量考生的准考证号
     */
    private void incrementTask(String projectId, String subjectId, String officeId, String admissionNum, Map<String, StudentExtendDTO> studentExtendMap) {
        List<Task> existTasks = taskService.getStudentSubjectTask(projectId, subjectId, admissionNum);
        if (existTasks == null || existTasks.size() == 0) {
            List<Piece> pieces = pieceService.lambdaQuery().eq(Piece::getProjectId, projectId)
                    .eq(Piece::getSubjectId, subjectId)
                    .eq(Piece::getDelFlag, false).list();
            Student student = studentService.getByAdmissionNum(projectId, subjectId, admissionNum);
            // 由于外面循环的科目是全量的，AB卷可能会出现null的情况，return即可
            if (null == student) {
                return;
            }
            List<Task> tasks = new ArrayList<>();
            for (Piece piece : pieces) {
                boolean paperPiece = CommonEnums.PIECE_TYPE_2.getCode().equals(piece.getPieceType());
                boolean paperIdEqual = piece.getPackageId() == null && piece.getPaperId() != null && piece.getPaperId().equals(student.getPaperId());
                boolean packageIdEqual = piece.getPackageId() != null && piece.getPackageId().equals(student.getPackageId());
                if (paperPiece && (paperIdEqual || packageIdEqual)) {
                    Task task = new Task();
                    String taskId = UUIDUtils.newSortUUID();
                    task.setId(taskId);
                    task.setProjectId(projectId);
                    task.setSubjectId(subjectId);
                    task.setAdmissionNum(admissionNum);
                    task.setEncodeId(admissionNum);
                    task.setPieceId(piece.getId());
                    task.setStage(Constants.MARK_STAGE);
                    task.setStatus(0);
                    // task 其他数据
                    StringBuilder sb = new StringBuilder();
                    sb.append(projectId);
                    sb.append(subjectId);
                    sb.append(admissionNum);
                    StudentExtendDTO studentExtendDTO = studentExtendMap.get(sb.toString());
                    extendTask(task, studentExtendDTO);
                    tasks.add(task);
                    pieceService.updateFinishStatus(projectId, subjectId, piece.getId(), false);
                }
            }
            taskService.saveBatch(tasks);
            if (tasks != null && tasks.size() > 0) {
                subjectService.updateSubjectMarkStatus(projectId, subjectId, false);
            }
        }
    }


    @Override
    public String projectDataDownload(String projectId, String officeId, String userId, String deptId) throws Exception {
        Project project = projectMapper.selectById(projectId);
        if (null == project) {
            return null;
        }
        // 导出文件名称
        String zipFileName = Optional.ofNullable(project)
                .map(Project::getProjectName)
                .map(data -> data + "评卷数据包" + DateUtil.format(DateUtil.date(), DatePattern.createFormatter(YYYYMMDDHHMM)) + ".zip")
                .orElse("项目所有文件数据包.zip");

        // 获取文件下载记录下载中，则直接告诉客户下载洪
        ProjectDataDownLoadRecord projectDataDownLoadRecord = projectDataDownLoadRecordService.getProjectDataDownLoadRecord(projectId, officeId, ProjectDownLoadFileTypeEnum.project_all_zip.getType());
        if (null != projectDataDownLoadRecord && ProjectDownLoadStatusEnum.downloading.getStatus().equals(projectDataDownLoadRecord.getStatus())) {
            // 判断文件是不是已经距离导出时间1小时内
            LocalDateTime createLocationTime = projectDataDownLoadRecord.getCreateDate();
            Date createDate = DateUtils.asDate(createLocationTime);
            Date createDateOneHourAfter = DateUtils.afterHoursToNowDate(createDate, downloadTimeinterval);
            Date now = new Date();
            if (now.compareTo(createDateOneHourAfter) < 0) {
                return ProjectDownLoadStatusEnum.downloading.getStatus();
            }
        }
        // 如果文件下载成功
        if (null != projectDataDownLoadRecord
                && ProjectDownLoadStatusEnum.finish.getStatus().equals(projectDataDownLoadRecord.getStatus())
                && StringUtils.isNotEmpty(projectDataDownLoadRecord.getFileUrl())) {
            // 获取下载记录的创建时间
            LocalDateTime recordCreateDate = projectDataDownLoadRecord.getCreateDate();
            // 获取项目下的考生最新的时间
            Student student = studentService.getLastOneStudentByProject(projectId);
            LocalDateTime studentCreateDate = student.getCreateDate();
            if (recordCreateDate.compareTo(studentCreateDate) > 0) {
                return projectDataDownLoadRecord.getFileUrl();
            }
        }
        projectDataDownLoadRecord = new ProjectDataDownLoadRecord();
        projectDataDownLoadRecord.setId(UUIDUtils.newSortUUID());
        projectDataDownLoadRecord.setProjectId(projectId);
        projectDataDownLoadRecord.setOfficeId(officeId);
        projectDataDownLoadRecord.setDeptId(deptId);
        projectDataDownLoadRecord.setFileType(ProjectDownLoadFileTypeEnum.project_all_zip.getType());
        projectDataDownLoadRecord.setStatus(ProjectDownLoadStatusEnum.downloading.getStatus());
        projectDataDownLoadRecord.setRemark(zipFileName);
        projectDataDownLoadRecord.setCreateBy(userId);
        projectDataDownLoadRecord.setCreateDate(LocalDateTime.now());
        projectDataDownLoadRecord.setUpdateBy(userId);
        projectDataDownLoadRecord.setDelFlag(false);
        projectDataDownLoadRecord.setUpdateDate(LocalDateTime.now());
        projectDataDownLoadRecordService.save(projectDataDownLoadRecord);


        // 异步导出zip文件
        ProjectDataDownLoadRecord finalProjectDataDownLoadRecord = projectDataDownLoadRecord;
        Future<String> fileFuture = ExecutorPoolUtil.getPoolTaskExecutor().submit(() -> {
            // 开启一个计时任务
            StopWatch stopWatch = StopWatch.create("导出文件整体统计：" + projectId);
            // zip文件URL
            String zipUrl = null;
            try {
                DownloadVo downloadVo = new DownloadVo();

                //判断是否有客观题
                Boolean projectHasObjectiveItems = Boolean.TRUE;
                // 获取项目下的所有客观题列表
                List<String> projectIds = new ArrayList<String>();
                projectIds.add(projectId);
                Map<String, List<PaperItem>> projectObjectiveItemMap = paperItemService.selectAllObjectiveItemsByProjectIds(new ArrayList<String>(projectIds));
                List<PaperItem> objectiveItems = projectObjectiveItemMap.get(projectId);
                if (CollectionUtils.isEmpty(objectiveItems)) {
                    projectHasObjectiveItems = Boolean.FALSE;
                }

                // 计算科目数据
                Future<List<SubjectVo>> subjectFuture = ExecutorPoolUtil.getPoolTaskExecutor().submit(new Callable<List<SubjectVo>>() {
                    @Override
                    public List<SubjectVo> call() throws Exception {
                        List<SubjectVo> result = projectDataDownLoadService.getSubjectVos(projectId, officeId);
                        return result;
                    }
                });

                if (projectHasObjectiveItems) {
                    // 计算客观题分数
                    Future<List<ObjectiveScoreVo>> objectiveScoreFuture = ExecutorPoolUtil.getPoolTaskExecutor().submit(new Callable<List<ObjectiveScoreVo>>() {
                        @Override
                        public List<ObjectiveScoreVo> call() throws Exception {
                            List<ObjectiveScoreVo> result = projectDataDownLoadService.getObjectiveScoreVos(projectId);
                            return result;
                        }
                    });
                    // 客观题分数信息
                    downloadVo.setObjectiveScoreVos(objectiveScoreFuture.get());
                }
                // 接口文档接口数据
                Future<List<InterfaceDocVo>> interfaceDocVoFuture = ExecutorPoolUtil.getPoolTaskExecutor().submit(new Callable<List<InterfaceDocVo>>() {
                    @Override
                    public List<InterfaceDocVo> call() throws Exception {
                        List<InterfaceDocVo> result = projectDataDownLoadService.getInterfaceDocVos(projectId, officeId);
                        return result;
                    }
                });

                //报名考生信息
                Future<List<StudentVo>> studentFuture = ExecutorPoolUtil.getPoolTaskExecutor().submit(new Callable<List<StudentVo>>() {
                    @Override
                    public List<StudentVo> call() throws Exception {
                        List<StudentVo> result = projectDataDownLoadService.getStudentVos(projectId);
                        return result;
                    }
                });

                stopWatch.start("02 获取科目信息");
                // 科目信息
                List<SubjectVo> subjectVo = subjectFuture.get();
                List<SubjectVo> subjectVos = new ArrayList<>();
                logger.info("# 01、导出zip文件{}获取科目信息完成", zipFileName);
                stopWatch.stop();

                stopWatch.start("03 获取题块信息");
                //题块信息
                List<TopicVO> topicVOList = new ArrayList<>();
                for (SubjectVo vo : subjectVo) {
                    Future<List<TopicVO>> topicVOFuture = ExecutorPoolUtil.getPoolTaskExecutor().submit(new Callable<List<TopicVO>>() {
                        @Override
                        public List<TopicVO> call() throws Exception {
                            List<TopicVO> result = projectDataDownLoadService.getTopicList(vo.getPaperId());
                            return result;
                        }
                    });
                    String subjectId = vo.getSubjectId().substring(0, 6);
                    List<TopicVO> list = topicVOFuture.get();
                    if (list.isEmpty()) {
                        vo.setSubjectId(subjectId);
                        subjectVos.add(vo);
                        continue;
                    }
                    List<TopicItemVO> itemVOList = new ArrayList<>();
                    for (TopicVO topicVO : list) {
                        topicVO.setSubjectId(subjectId);
                        if (null != topicVO.getItemVOList()) {
                            List<TopicItemVO> voList = topicVO.getItemVOList();
                            voList.forEach(p -> p.setSubjectId(subjectId));
                            itemVOList.addAll(voList);
                        }
                    }
                    vo.setSubjectId(subjectId);
                    vo.setTopicVOList(list);
                    vo.setItemVOList(itemVOList);
                    topicVOList.addAll(list);
                    subjectVos.add(vo);
                }
                logger.info("# 02、导出zip文件{}获取题块信息完成", zipFileName);
                stopWatch.stop();

                downloadVo.setProjectId(projectId);
                downloadVo.setZipFileName(zipFileName);

                // 科目信息
                downloadVo.setSubjectVos(subjectVos);
                // 题目定义
                downloadVo.setTopicVOs(topicVOList);

                logger.info("# 03、导出zip文件{}获取客观题分数信息完成", zipFileName);

                stopWatch.start("04 获取报名考生信息");
                //报名考生信息
                downloadVo.setStudentVos(studentFuture.get());
                logger.info("# 04、导出zip文件{}获取报名考生信息完成", zipFileName);
                stopWatch.stop();

                stopWatch.start("05 获取接口数据文件");
                // 接口文件数据
                downloadVo.setInterfaceDocVos(interfaceDocVoFuture.get());
                logger.info("# 05、导出zip文件{}获取接口数据文件信息完成", zipFileName);
                stopWatch.stop();

                stopWatch.start("06 生成ZIP上传华为云");
                // 导出下载的zip文件
                zipUrl = this.exportDownloadZip(downloadVo);
                stopWatch.stop();
            } catch (Exception e) {
                logger.error("# 23、上传{}文件异常", zipFileName, e);
            } finally {
                logger.info("# 24、{}操作完整时间：{}{}", zipFileName, FileUtil.getLineSeparator(), stopWatch.prettyPrint(TimeUnit.SECONDS));
            }
            // 更新文件下载记录
            if (StringUtils.isEmpty(zipUrl)) {
                // 更新文件下载记录
                finalProjectDataDownLoadRecord.setStatus(ProjectDownLoadStatusEnum.fail.getStatus());
            } else {
                // 更新文件下载记录
                finalProjectDataDownLoadRecord.setStatus(ProjectDownLoadStatusEnum.finish.getStatus());
            }
            finalProjectDataDownLoadRecord.setUpdateDate(LocalDateTime.now());
            finalProjectDataDownLoadRecord.setFileUrl(zipUrl);
            projectDataDownLoadRecordService.updateById(finalProjectDataDownLoadRecord);
            return zipUrl;
        });

        String zipUrl = null;
        try {
            zipUrl = ExecutorPoolUtil.getPoolTaskExecutor()
                    .submit(() -> {
                        // 这里浅浅的睡眠100毫秒避免空转
                        while (!fileFuture.isDone()) {
                            ThreadUtil.sleep(100);
                        }
                        return fileFuture.get();
                    }).get(globalConfig.getFileTimeOut(), TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            logger.error("# 22、{}文件生成超过指定等待时间，提前返回", zipFileName);
        }
        return zipUrl;
    }

    @Override
    public String dkbProjectDataDownload(String projectId, String officeId, String userId, String deptId) throws Exception {
        Project project = projectMapper.selectById(projectId);
        if (null == project) {
            return null;
        }
        // 导出文件名称
        String zipFileName = Optional.of(project)
                .map(Project::getProjectName)
                .map(data -> data + "大考版评卷数据包" + DateUtil.format(DateUtil.date(), DatePattern.createFormatter(YYYYMMDDHHMM)) + ".zip")
                .orElse("项目所有文件数据包.zip");

        // 获取文件下载记录下载中，则直接告诉客户下载洪
        ProjectDataDownLoadRecord projectDataDownLoadRecord = projectDataDownLoadRecordService.getProjectDataDownLoadRecord(projectId, officeId, ProjectDownLoadFileTypeEnum.dkb_project_all_zip.getType());
        if (null != projectDataDownLoadRecord && ProjectDownLoadStatusEnum.downloading.getStatus().equals(projectDataDownLoadRecord.getStatus())) {
            // 判断文件是不是已经距离导出时间1小时内
            LocalDateTime createLocationTime = projectDataDownLoadRecord.getCreateDate();
            Date createDate = DateUtils.asDate(createLocationTime);
            Date createDateOneHourAfter = DateUtils.afterHoursToNowDate(createDate, downloadTimeinterval);
            Date now = new Date();
            if (now.compareTo(createDateOneHourAfter) < 0) {
                return ProjectDownLoadStatusEnum.downloading.getStatus();
            }
        }
        // 如果文件下载成功
        if (null != projectDataDownLoadRecord
                && ProjectDownLoadStatusEnum.finish.getStatus().equals(projectDataDownLoadRecord.getStatus())
                && StringUtils.isNotEmpty(projectDataDownLoadRecord.getFileUrl())) {
            // 获取下载记录的创建时间
            LocalDateTime recordCreateDate = projectDataDownLoadRecord.getCreateDate();
            // 获取项目下的考生最新的时间
            Student student = studentService.getLastOneStudentByProject(projectId);
            LocalDateTime studentCreateDate = student.getCreateDate();
            if (recordCreateDate.compareTo(studentCreateDate) > 0) {
                return projectDataDownLoadRecord.getFileUrl();
            }
        }
        projectDataDownLoadRecord = new ProjectDataDownLoadRecord();
        projectDataDownLoadRecord.setId(UUIDUtils.newSortUUID());
        projectDataDownLoadRecord.setProjectId(projectId);
        projectDataDownLoadRecord.setOfficeId(officeId);
        projectDataDownLoadRecord.setDeptId(deptId);
        projectDataDownLoadRecord.setFileType(ProjectDownLoadFileTypeEnum.dkb_project_all_zip.getType());
        projectDataDownLoadRecord.setStatus(ProjectDownLoadStatusEnum.downloading.getStatus());
        projectDataDownLoadRecord.setRemark(zipFileName);
        projectDataDownLoadRecord.setCreateBy(userId);
        projectDataDownLoadRecord.setCreateDate(LocalDateTime.now());
        projectDataDownLoadRecord.setUpdateBy(userId);
        projectDataDownLoadRecord.setDelFlag(false);
        projectDataDownLoadRecord.setUpdateDate(LocalDateTime.now());
        projectDataDownLoadRecordService.save(projectDataDownLoadRecord);

        // 异步导出zip文件
        ProjectDataDownLoadRecord finalProjectDataDownLoadRecord = projectDataDownLoadRecord;
        Future<String> fileFuture = ExecutorPoolUtil.getPoolTaskExecutor().submit(() -> {
            // zip文件URL
            String zipUrl = null;
            try {
                DkbDownloadVo downloadVo = new DkbDownloadVo();

                //判断是否有客观题
                Boolean projectHasObjectiveItems = Boolean.TRUE;
                // 获取项目下的所有客观题列表
                List<String> projectIds = new ArrayList<String>();
                projectIds.add(projectId);
                Map<String, List<PaperItem>> projectObjectiveItemMap = paperItemService.selectAllObjectiveItemsByProjectIds(new ArrayList<String>(projectIds));
                List<PaperItem> objectiveItems = projectObjectiveItemMap.get(projectId);
                if (CollectionUtils.isEmpty(objectiveItems)) {
                    projectHasObjectiveItems = Boolean.FALSE;
                }

                // 计算科目数据
                List<SubjectVo> subjectVos = projectDataDownLoadService.getDkbSubjectVos(projectId, officeId);

                //报名考生信息
                Map<String, List<StudentVo>> studenMap = projectDataDownLoadService.getStudentVosMap(projectId, subjectVos);

                if (projectHasObjectiveItems) {
                    // 计算客观题分数
                    Future<Map<String, List<ObjectiveScoreVo>>> objectiveScoreFuture = ExecutorPoolUtil.getPoolTaskExecutor().submit(new Callable<Map<String, List<ObjectiveScoreVo>>>() {
                        @Override
                        public Map<String, List<ObjectiveScoreVo>> call() throws Exception {
                            Map<String, List<ObjectiveScoreVo>> result = projectDataDownLoadService.getObjectiveScoreVoMaps(projectId, subjectVos);
                            return result;
                        }
                    });
                    // 客观题分数信息
                    downloadVo.setObjectiveScoreVos(objectiveScoreFuture.get());
                }

                // 接口文档接口数据
                Future<Map<String, List<InterfaceDocVo>>> interfaceDocVoFuture = ExecutorPoolUtil.getPoolTaskExecutor().submit(new Callable<Map<String, List<InterfaceDocVo>>>() {
                    @Override
                    public Map<String, List<InterfaceDocVo>> call() throws Exception {
                        Map<String, List<InterfaceDocVo>> result = projectDataDownLoadService.getInterfaceDocVoMaps(projectId, subjectVos, studenMap);
                        return result;
                    }
                });

                // 科目信息
                List<SubjectVo> subjectVosTemp = new ArrayList<>();
                logger.info("# 01、导出zip文件{}获取科目信息完成", zipFileName);
                //题块信息
                List<TopicVO> topicVOList = new ArrayList<>();
                for (Iterator<SubjectVo> iterator = subjectVos.iterator(); iterator.hasNext(); ) {
                    SubjectVo vo = iterator.next();
                    Future<List<TopicVO>> topicVOFuture = ExecutorPoolUtil.getPoolTaskExecutor().submit(new Callable<List<TopicVO>>() {
                        @Override
                        public List<TopicVO> call() throws Exception {
                            List<TopicVO> result = projectDataDownLoadService.getTopicList(vo.getPaperId());
                            return result;
                        }
                    });
                    String subjectId = vo.getSubjectId().substring(0, 6);
                    List<TopicVO> list = topicVOFuture.get();
                    if (list.isEmpty()) {
                        vo.setSubjectId(subjectId);
                        subjectVosTemp.add(vo);
                        continue;
                    }
                    List<TopicItemVO> itemVOList = new ArrayList<>();
                    for (TopicVO topicVO : list) {
                        topicVO.setSubjectId(subjectId);
                        if (null != topicVO.getItemVOList()) {
                            List<TopicItemVO> voList = topicVO.getItemVOList();
                            voList.forEach(p -> p.setSubjectId(subjectId));
                            itemVOList.addAll(voList);
                        }
                    }
                    vo.setSubjectId(subjectId);
                    vo.setTopicVOList(list);
                    vo.setItemVOList(itemVOList);
                    topicVOList.addAll(list);
                    subjectVosTemp.add(vo);
                }
                logger.info("# 02、导出zip文件{}获取题块信息完成", zipFileName);

                downloadVo.setProjectId(projectId);
                downloadVo.setZipFileName(zipFileName);

                // 科目信息
                downloadVo.setSubjectVos(subjectVosTemp);
                // 题目定义
                downloadVo.setTopicVOs(topicVOList);

                logger.info("# 03、导出zip文件{}获取客观题分数信息完成", zipFileName);
                //报名考生信息
                downloadVo.setStudentVos(studenMap);
                logger.info("# 04、导出zip文件{}获取报名考生信息完成", zipFileName);
                // 接口文件数据
                downloadVo.setInterfaceDocVos(interfaceDocVoFuture.get());
                logger.info("# 05、导出zip文件{}获取接口数据文件信息完成", zipFileName);

                // 导出下载的zip文件
                zipUrl = this.exportDownloadZip(downloadVo);
            } catch (Exception e) {
                logger.error("# 23、上传{}文件异常", zipFileName, e);
            }
            // 更新文件下载记录
            if (StringUtils.isEmpty(zipUrl)) {
                // 更新文件下载记录
                finalProjectDataDownLoadRecord.setStatus(ProjectDownLoadStatusEnum.fail.getStatus());
            } else {
                // 更新文件下载记录
                finalProjectDataDownLoadRecord.setStatus(ProjectDownLoadStatusEnum.finish.getStatus());
            }
            finalProjectDataDownLoadRecord.setUpdateDate(LocalDateTime.now());
            finalProjectDataDownLoadRecord.setFileUrl(zipUrl);
            projectDataDownLoadRecordService.updateById(finalProjectDataDownLoadRecord);
            return zipUrl;
        });

        String zipUrl = null;
        try {
            zipUrl = ExecutorPoolUtil.getPoolTaskExecutor()
                    .submit(() -> {
                        // 这里浅浅的睡眠100毫秒避免空转
                        while (!fileFuture.isDone()) {
                            ThreadUtil.sleep(100);
                        }
                        return fileFuture.get();
                    }).get(globalConfig.getFileTimeOut(), TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            logger.error("# 22、{}文件生成超过指定等待时间，提前返回", zipFileName);
        }
        return zipUrl;
    }

    @Override
    public String objectiveScoreDataDownload(String projectId, String officeId, String userId, String deptId, HttpServletResponse response) throws Exception {
        Project project = projectMapper.selectById(projectId);
        if (null == project) {
            return null;
        }
        // 获取文件下载记录下载中，则直接告诉客户下载洪
        ProjectDataDownLoadRecord projectDataDownLoadRecord = projectDataDownLoadRecordService.getProjectDataDownLoadRecord(projectId, officeId, ProjectDownLoadFileTypeEnum.project_objective_score.getType());
        if (null != projectDataDownLoadRecord && ProjectDownLoadStatusEnum.downloading.getStatus().equals(projectDataDownLoadRecord.getStatus())) {
            // 判断文件是不是已经距离导出时间1小时内
            LocalDateTime createLocationTime = projectDataDownLoadRecord.getCreateDate();
            Date createDate = DateUtils.asDate(createLocationTime);
            Date createDateOneHourAfter = DateUtils.afterHoursToNowDate(createDate, downloadTimeinterval);
            Date now = new Date();
            if (now.compareTo(createDateOneHourAfter) < 0) {
                return ProjectDownLoadStatusEnum.downloading.getStatus();
            }
        }
        // 如果文件下载成功
        if (null != projectDataDownLoadRecord && ProjectDownLoadStatusEnum.finish.getStatus().equals(projectDataDownLoadRecord.getStatus()) && StringUtils.isNotEmpty(projectDataDownLoadRecord.getFileUrl())) {
            // 获取下载记录的创建时间
            LocalDateTime recordCreateDate = projectDataDownLoadRecord.getCreateDate();
            // 获取项目下的考生最新的时间
            Student student = studentService.getLastOneStudentByProject(projectId);
            if (null == student) {
                return ProjectDownLoadStatusEnum.fail.getStatus();
            }
            LocalDateTime studentCreateDate = student.getCreateDate();
            if (recordCreateDate.compareTo(studentCreateDate) > 0) {
                return projectDataDownLoadRecord.getFileUrl();
            }
        }
        projectDataDownLoadRecord = new ProjectDataDownLoadRecord();
        projectDataDownLoadRecord.setId(UUIDUtils.newSortUUID());
        projectDataDownLoadRecord.setProjectId(projectId);
        projectDataDownLoadRecord.setOfficeId(officeId);
        projectDataDownLoadRecord.setDeptId(deptId);
        projectDataDownLoadRecord.setFileType(ProjectDownLoadFileTypeEnum.project_objective_score.getType());
        projectDataDownLoadRecord.setStatus(ProjectDownLoadStatusEnum.downloading.getStatus());
        projectDataDownLoadRecord.setRemark(ProjectDownLoadFileTypeEnum.project_objective_score.getDesc());
        projectDataDownLoadRecord.setCreateBy(userId);
        projectDataDownLoadRecord.setCreateDate(LocalDateTime.now());
        projectDataDownLoadRecord.setUpdateBy(userId);
        projectDataDownLoadRecord.setDelFlag(false);
        projectDataDownLoadRecord.setUpdateDate(LocalDateTime.now());
        projectDataDownLoadRecordService.save(projectDataDownLoadRecord);
        // 计算客观题分数
        Future<List<ObjectiveScoreVo>> objectiveScoreFuture = ExecutorPoolUtil.getPoolTaskExecutor().submit(new Callable<List<ObjectiveScoreVo>>() {
            @Override
            public List<ObjectiveScoreVo> call() throws Exception {
                List<ObjectiveScoreVo> result = projectDataDownLoadService.getObjectiveScoreVos(projectId);
                return result;
            }
        });

        //考试名称+客观题成绩+时间戳（年月日时分）
        StringBuilder sb = new StringBuilder();
        sb.append(project.getProjectName())
                .append("客观题成绩")
                .append(DateUtils.currentDatetime(DateUtils.YYYYMMDDHHMMSS))
                .append(".dbf");
        String fileName = sb.toString();

        // 生成dbf文件
        String dbfUrl = this.assembleKgtcjDbfStream(objectiveScoreFuture.get(), (file, inputStream) -> {
            // 上传到华为云
            return this.uploadFileToHuawei(IoUtil.readBytes(inputStream), "dbf", fileName);
        });
        if (StringUtils.isEmpty(dbfUrl)) {
            // 更新文件下载记录
            projectDataDownLoadRecord.setStatus(ProjectDownLoadStatusEnum.fail.getStatus());
        } else {
            // 更新文件下载记录
            projectDataDownLoadRecord.setStatus(ProjectDownLoadStatusEnum.finish.getStatus());
        }
        projectDataDownLoadRecord.setUpdateDate(LocalDateTime.now());
        projectDataDownLoadRecord.setFileUrl(dbfUrl);
        projectDataDownLoadRecordService.updateById(projectDataDownLoadRecord);
        return dbfUrl;
    }


    @Override
    public String dkbObjectiveScoreDataDownload(String projectId, String officeId, String userId, String deptId, HttpServletResponse response) throws Exception {
        Project project = projectMapper.selectById(projectId);
        if (null == project) {
            return null;
        }
        // 获取文件下载记录下载中，则直接告诉客户下载洪
        ProjectDataDownLoadRecord projectDataDownLoadRecord = projectDataDownLoadRecordService.getProjectDataDownLoadRecord(projectId, officeId, ProjectDownLoadFileTypeEnum.dkb_project_objective_score.getType());
        if (null != projectDataDownLoadRecord && ProjectDownLoadStatusEnum.downloading.getStatus().equals(projectDataDownLoadRecord.getStatus())) {
            // 判断文件是不是已经距离导出时间1小时内
            LocalDateTime createLocationTime = projectDataDownLoadRecord.getCreateDate();
            Date createDate = DateUtils.asDate(createLocationTime);
            Date createDateOneHourAfter = DateUtils.afterHoursToNowDate(createDate, downloadTimeinterval);
            Date now = new Date();
            if (now.compareTo(createDateOneHourAfter) < 0) {
                return ProjectDownLoadStatusEnum.downloading.getStatus();
            }
        }
        // 如果文件下载成功
        if (null != projectDataDownLoadRecord && ProjectDownLoadStatusEnum.finish.getStatus().equals(projectDataDownLoadRecord.getStatus()) && StringUtils.isNotEmpty(projectDataDownLoadRecord.getFileUrl())) {
            // 获取下载记录的创建时间
            LocalDateTime recordCreateDate = projectDataDownLoadRecord.getCreateDate();
            // 获取项目下的考生最新的时间
            Student student = studentService.getLastOneStudentByProject(projectId);
            if (null == student) {
                return ProjectDownLoadStatusEnum.fail.getStatus();
            }
            LocalDateTime studentCreateDate = student.getCreateDate();
            if (recordCreateDate.compareTo(studentCreateDate) > 0) {
                return projectDataDownLoadRecord.getFileUrl();
            }
        }
        projectDataDownLoadRecord = new ProjectDataDownLoadRecord();
        projectDataDownLoadRecord.setId(UUIDUtils.newSortUUID());
        projectDataDownLoadRecord.setProjectId(projectId);
        projectDataDownLoadRecord.setOfficeId(officeId);
        projectDataDownLoadRecord.setDeptId(deptId);
        projectDataDownLoadRecord.setFileType(ProjectDownLoadFileTypeEnum.dkb_project_objective_score.getType());
        projectDataDownLoadRecord.setStatus(ProjectDownLoadStatusEnum.downloading.getStatus());
        projectDataDownLoadRecord.setRemark(ProjectDownLoadFileTypeEnum.dkb_project_objective_score.getDesc());
        projectDataDownLoadRecord.setCreateBy(userId);
        projectDataDownLoadRecord.setCreateDate(LocalDateTime.now());
        projectDataDownLoadRecord.setUpdateBy(userId);
        projectDataDownLoadRecord.setDelFlag(false);
        projectDataDownLoadRecord.setUpdateDate(LocalDateTime.now());
        projectDataDownLoadRecordService.save(projectDataDownLoadRecord);

        // 计算科目数据
        List<SubjectVo> subjectVos = projectDataDownLoadService.getDkbSubjectVos(projectId, officeId);
        // 计算客观题分数
        Future<Map<String, List<ObjectiveScoreVo>>> objectiveScoreFuture = ExecutorPoolUtil.getPoolTaskExecutor().submit(new Callable<Map<String, List<ObjectiveScoreVo>>>() {
            @Override
            public Map<String, List<ObjectiveScoreVo>> call() throws Exception {
                Map<String, List<ObjectiveScoreVo>> result = projectDataDownLoadService.getObjectiveScoreVoMaps(projectId, subjectVos);
                return result;
            }
        });

        //考试名称+客观题成绩+时间戳（年月日时分）
        StringBuilder sb = new StringBuilder();
        sb.append(project.getProjectName())
                .append("客观题成绩")
                .append(DateUtils.currentDatetime(DateUtils.YYYYMMDDHHMMSS))
                .append(".zip");
        String fileName = sb.toString();

        Map<String, String> subjectMapping = subjectVos
                .stream()
                .collect(Collectors.toMap(SubjectVo::getOrgSubjectId, SubjectVo::getSubjectName));

        // 导入客观题成绩文件txt文件异步生成(如果乜有客观题则不生成文件)
        Future<Map<String, File>> kgtcjDbfFileFuture = this.asyncKgtcjTxtFile(objectiveScoreFuture.get(), subjectMapping);
        // 导入客观题成绩文件dbf文件到zip中
        Map<String, File> kgtcjDbfFiles = kgtcjDbfFileFuture.get();
        String zipUrl = null;

        File file = FileUtil.createTempFile(".zip", true);
        file.deleteOnExit();
        FileInputStream fileInputStream = null;
        ZipOutputStream zipOutputStream = null;
        try {
            fileInputStream = new FileInputStream(file);
            zipOutputStream = new ZipOutputStream(Files.newOutputStream(file.toPath()));

            this.assembleKgtcjToZip(kgtcjDbfFiles, zipOutputStream);
            // 将zip流刷出去
            zipOutputStream.flush();
            IoUtil.close(zipOutputStream);

            // 将文件字节数组上传到华为云
            zipUrl = this.uploadFileToHuawei(file, "zip", fileName);
        } catch (Exception e) {
            logger.error("# 21、导出下载的zip文件{}异常", fileName, e);
        } finally {
            IoUtil.close(fileInputStream);
            IoUtil.close(zipOutputStream);
            FileUtil.del(file);
        }

        if (StringUtils.isEmpty(zipUrl)) {
            // 更新文件下载记录
            projectDataDownLoadRecord.setStatus(ProjectDownLoadStatusEnum.fail.getStatus());
        } else {
            // 更新文件下载记录
            projectDataDownLoadRecord.setStatus(ProjectDownLoadStatusEnum.finish.getStatus());
        }
        projectDataDownLoadRecord.setUpdateDate(LocalDateTime.now());
        projectDataDownLoadRecord.setFileUrl(zipUrl);
        projectDataDownLoadRecordService.updateById(projectDataDownLoadRecord);
        return zipUrl;
    }

    /**
     * 导出下载的zip文件
     *
     * @param downloadVo 导出dto
     */
    private String exportDownloadZip(DownloadVo downloadVo) {
        File file = FileUtil.createTempFile(".zip", true);
        file.deleteOnExit();
        FileInputStream fileInputStream = null;
        ZipOutputStream zipOutputStream = null;
        try {
            fileInputStream = new FileInputStream(file);
            zipOutputStream = new ZipOutputStream(new FileOutputStream(file));
            logger.info("# 06、开始生成zip文件：{}", downloadVo.getZipFileName());
            // 导入报名库dbf文件异步生成
            Future<File> asynBmkFileFuture = this.asynBmkFile(downloadVo.getStudentVos());

            if (!CollectionUtils.isEmpty(downloadVo.getObjectiveScoreVos())) {
                // 导入客观题成绩文件dbf文件异步生成(如果乜有客观题则不生成文件)
                Future<File> kgtcjDbfFileFuture = this.asyncKgtcjDbfFile(downloadVo.getObjectiveScoreVos());
                // 导入客观题成绩文件dbf文件到zip中
                File kgtcjDbfFile = kgtcjDbfFileFuture.get();
                this.assembleKgtcjToZip(kgtcjDbfFile, zipOutputStream);
            }

            logger.info("# 09、导入客观题成绩文件dbf文件到{}完成", downloadVo.getZipFileName());
            // 异步导入题结构文件txt文件生成
            Future<Map<String, Future<File>>> asynItemStructureFileFutures = this.asynItemStructureFile(downloadVo.getSubjectVos());

            // 导入接口文件txt文件到zip中
            this.assembleInterfaceToZip(downloadVo.getProjectId(), downloadVo.getInterfaceDocVos(), zipOutputStream);
            logger.info("# 07、导入接口文件txt文件到{}完成", downloadVo.getZipFileName());
            // 导入科目excel文件装配到zip中
            this.assembleExcelToZip(downloadVo.getSubjectVos(), zipOutputStream);
            logger.info("# 08、导入科目excel文件到{}完成", downloadVo.getZipFileName());

            // 导入题结构文件txt文件到zip中
            Map<String, Future<File>> itemStructureFileFutureMap = asynItemStructureFileFutures.get();
            this.assembleItemStructureToZip(itemStructureFileFutureMap, zipOutputStream);
            logger.info("# 10、导入题结构文件txt文件到{}完成", downloadVo.getZipFileName());
            // 导入报名库dbf文件到zip中
            File asynBmkFile = asynBmkFileFuture.get();
            this.assembleBmkToZip(asynBmkFile, zipOutputStream);
            logger.info("# 11、导入报名库dbf文件到{}完成", downloadVo.getZipFileName());

            // 将zip流刷出去
            zipOutputStream.flush();
            IoUtil.close(zipOutputStream);

            logger.info("# 12、文件{}生成成功", downloadVo.getZipFileName());
            // 将文件字节数组上传到华为云
            String zipUrl = this.uploadFileToHuawei(file, "zip", downloadVo.getZipFileName());
            logger.info("# 20、文件{}上传华为云成功", downloadVo.getZipFileName());
            return zipUrl;
        } catch (InterruptedException e) {
            logger.error("# 21、导出下载的zip文件{}异常", downloadVo.getZipFileName(), e);
            Thread.currentThread().interrupt();
        } catch (Exception e) {
            logger.error("# 21、导出下载的zip文件{}异常", downloadVo.getZipFileName(), e);
        } finally {
            IoUtil.close(fileInputStream);
            IoUtil.close(zipOutputStream);
            FileUtil.del(file);
        }
        return null;
    }


    /**
     * 导出下载的zip文件
     *
     * @param downloadVo 导出dto
     */
    private String exportDownloadZip(DkbDownloadVo downloadVo) {
        File file = FileUtil.createTempFile(".zip", true);
        file.deleteOnExit();
        FileInputStream fileInputStream = null;
        ZipOutputStream zipOutputStream = null;
        try {
            fileInputStream = new FileInputStream(file);
            zipOutputStream = new ZipOutputStream(Files.newOutputStream(file.toPath()));
            logger.info("# 06、开始生成zip文件：{}", downloadVo.getZipFileName());

            Map<String, String> subjectMapping = downloadVo.getSubjectVos()
                    .stream()
                    .collect(Collectors.toMap(SubjectVo::getOrgSubjectId, SubjectVo::getSubjectName));

            // 导入报名库txt文件异步生成
            Future<Map<String, File>> asynBmkFileFuture = this.asynBmkFile(downloadVo.getStudentVos(), subjectMapping);

            if (MapUtils.isNotEmpty(downloadVo.getObjectiveScoreVos())) {
                // 导入客观题成绩文件txt文件异步生成(如果乜有客观题则不生成文件)
                Future<Map<String, File>> kgtcjDbfFileFuture = this.asyncKgtcjTxtFile(downloadVo.getObjectiveScoreVos(), subjectMapping);
                // 导入客观题成绩文件dbf文件到zip中
                Map<String, File> kgtcjDbfFiles = kgtcjDbfFileFuture.get();
                this.assembleKgtcjToZip(kgtcjDbfFiles, zipOutputStream);
            }

            logger.info("# 09、导入客观题成绩文件dbf文件到{}完成", downloadVo.getZipFileName());
            // 异步导入题结构文件txt文件生成
            Future<Map<String, Future<File>>> asynItemStructureFileFutures = this.asynItemStructureFile(downloadVo.getSubjectVos());

            // 导入接口文件txt文件到zip中
            this.assembleInterfaceToZip(downloadVo.getInterfaceDocVos(), zipOutputStream, subjectMapping);
            logger.info("# 07、导入接口文件txt文件到{}完成", downloadVo.getZipFileName());
            // 导入科目excel文件装配到zip中
            this.assembleExcelToZip(downloadVo.getSubjectVos(), zipOutputStream);
            logger.info("# 08、导入科目excel文件到{}完成", downloadVo.getZipFileName());

            // 导入题结构文件txt文件到zip中
            Map<String, Future<File>> itemStructureFileFutureMap = asynItemStructureFileFutures.get();
            this.assembleItemStructureToZip(itemStructureFileFutureMap, zipOutputStream);
            logger.info("# 10、导入题结构文件txt文件到{}完成", downloadVo.getZipFileName());
            // 导入报名库dbf文件到zip中
            Map<String, File> asynBmkFileMap = asynBmkFileFuture.get();
            this.assembleBmkToZip(asynBmkFileMap, zipOutputStream);
            logger.info("# 11、导入报名库dbf文件到{}完成", downloadVo.getZipFileName());

            // 将zip流刷出去
            zipOutputStream.flush();
            IoUtil.close(zipOutputStream);

            logger.info("# 12、文件{}生成成功", downloadVo.getZipFileName());
            // 将文件字节数组上传到华为云
            String zipUrl = this.uploadFileToHuawei(file, "zip", downloadVo.getZipFileName());
            logger.info("# 20、文件{}上传华为云成功", downloadVo.getZipFileName());
            return zipUrl;
        } catch (InterruptedException e) {
            logger.error("# 21、导出下载的zip文件{}异常", downloadVo.getZipFileName(), e);
            Thread.currentThread().interrupt();
        } catch (Exception e) {
            logger.error("# 21、导出下载的zip文件{}异常", downloadVo.getZipFileName(), e);
        } finally {
            IoUtil.close(fileInputStream);
            IoUtil.close(zipOutputStream);
            FileUtil.del(file);
        }
        return null;
    }

    /**
     * 将文件字节数组上传到华为云
     *
     * @param fileByte 文件字节数组
     * @param fileType 文件类型
     * @param fileName 文件名称
     * @return
     */
    private String uploadFileToHuawei(byte[] fileByte, String fileType, String fileName) {
        String fileUrl = null;
        // 获取华为云上传路径
        String hwCloudFileDict = globalConfig.getHwCloudFileDict();
        String path = hwCloudFileDict + fileType + "/" + fileName;
        // 文件上传华为云
        logger.info("# {}文件开始上传华为云", fileType);
        Result result = uploadFileToHuawei("", path, fileByte);
        logger.info("# {}上传华为云：{}", fileType, JSON.toJSONString(result));
        if (result.getSuccess()) {
            fileUrl = result.getObj().toString().replace("%2F", "/");
        }
        return fileUrl;
    }

    /**
     * 将文件字节数组上传到华为云
     *
     * @param file     文件
     * @param fileType 文件类型
     * @param fileName 文件名称
     * @return
     */
    private String uploadFileToHuawei(File file, String fileType, String fileName) {
        String fileUrl = null;
        // 获取华为云上传路径
        String hwCloudFileDict = globalConfig.getHwCloudFileDict();
        String path = hwCloudFileDict + fileType + "/" + fileName;
        // 文件上传华为云
        logger.info("# 13、{}文件{}开始上传华为云", fileType, fileName);
        Result result = uploadFileToHuawei("", path, file);
        logger.info("# 19、{}文件{}上传华为云：{}", fileType, fileName, JSON.toJSONString(result));
        if (result.getSuccess()) {
            fileUrl = result.getObj().toString().replace("%2F", "/");
        }
        return fileUrl;
    }

    public Result uploadFileToHuawei(String officeId, String path, byte[] bytes) {
        Result result = new Result();
        try {
            result.setSuccess(true);
            result.setObj(uploadFile(path, bytes));
        } catch (Exception e) {
            logger.error("# 华为云上传文件调用失败：{}", path, e);
            result.setResult(false, "华为云上传文件调用失败");
        }
        return result;
    }

    public Result uploadFileToHuawei(String officeId, String path, File file) {
        Result result = new Result();
        try {
            result.setSuccess(true);
            result.setObj(uploadFile(path, file));
        } catch (Exception e) {
            logger.error("# 18、华为云上传文件调用失败：{}", path, e);
            result.setResult(false, "华为云上传文件调用失败");
        }
        return result;
    }

    private Object uploadFile(String path, byte[] bytes) {
        Bucket bucket = projectDataDownLoadService.getBucketConfig();
        PutObjectResult putObjectResult = projectDataDownLoadService.getObsClient().putObject(bucket.getBucket(), path, new ByteArrayInputStream(bytes), null);
        int statusCode = putObjectResult.getStatusCode();
        if (statusCode != HttpServletResponse.SC_OK) {
            throw new RuntimeException("华为云OBS文件上传异常");
        }
        logger.info("# 华为云存储上传文件返回路径：{}", putObjectResult.getObjectUrl());
        return putObjectResult.getObjectUrl();
    }

    /**
     * 断点续传文件上传
     *
     * @param path
     * @param file
     */
    private String uploadFile(String path, File file) {
        Bucket bucket = projectDataDownLoadService.getBucketConfig();
        UploadFileRequest request = new UploadFileRequest(bucket.getBucket(), path);
        // 设置待上传的本地文件，localfile为待上传的本地文件路径，需要指定到具体的文件名
        request.setUploadFile(file.getPath());
        // 设置分段上传时的最大并发数
        request.setTaskNum(globalConfig.getTaskNum());
        // 设置分段大小为100MB
        request.setPartSize(globalConfig.getPartSize() * MB_SPEED_CONVERT);
        // 开启断点续传模式
        request.setEnableCheckpoint(true);
        // 设置数据传输监听器，用于获取上传进度
        request.setProgressListener(status -> {
            try {
                // 获取上传平均速率
                logger.info("{}文件上传平均速率:{}MB/S", path, BigDecimal.valueOf(status.getAverageSpeed() / MB_SPEED_CONVERT).setScale(CommonConstant.TWO, RoundingMode.HALF_DOWN));
                // 获取上传进度百分比
                logger.info("{}文件上传进度百分比:{}%", path, status.getTransferPercentage());
            } catch (Exception e) {

            }
        });
        // 数据反馈上传进度
        request.setProgressInterval(globalConfig.getProgressInterval() * MB_SPEED_CONVERT);
        logger.info("# 14、{}断点续传请求信息：{}", path, request);
        // 进行断点续传上传
        return uploadFile(request);
    }

    /**
     * 文件断点续传到华为云
     * 在这里会进行失败重试
     *
     * @param request
     * @return
     */
    private String uploadFile(UploadFileRequest request) {
        // 是否异常
        boolean hasError;
        // 文件上传异常次数
        Integer exceptionNum = 0;
        // 文件上传成功url
        String fileUrl = null;
        // 上传开始时间
        DateTime startDateTime = DateUtil.date();

        ObsClient obsClient = projectDataDownLoadService.getObsClient();
        do {
            hasError = Boolean.FALSE;
            try {
                // 进行断点续传上传
                CompleteMultipartUploadResult result = obsClient.uploadFile(request);
                logger.info("# 15、断点续传响应信息：{}", result);
                fileUrl = result.getObjectUrl();
            } catch (ObsException e) {
                // 发生异常时可再次调用断点续传上传接口进行重新上传
                hasError = Boolean.TRUE;
                exceptionNum++;
                // 是否大于重试次数
                if (exceptionNum > globalConfig.getRetryNum()) {
                    throw new BusinessException(StrUtil.format("文件{}上传异常，失败重试次数已超最大值", request.getObjectKey()));
                }
                logger.error("# 16、{}文件上传异常，失败重试第{}次", request.getObjectKey(), exceptionNum);
            }
        } while (hasError);
        // 上传结束时间
        DateTime endDateTime = DateUtil.date();
        logger.info("# 17、文件上传统计：{}文件上传开始时间{}，上传结束时间{}，上传失败{}次，总耗时{}", request.getObjectKey(), startDateTime, endDateTime, exceptionNum, DateUtil.formatBetween(startDateTime, endDateTime));
        return fileUrl;
    }

    /**
     * 装配excel到zip包中
     *
     * @param subjectVos      科目信息VO
     * @param zipOutputStream zip包流
     * @throws IOException
     */
    private void assembleExcelToZip(List<SubjectVo> subjectVos, ZipOutputStream zipOutputStream) throws Exception {
        if (CollUtil.isEmpty(subjectVos)) {
            return;
        }
        ExcelWriter writer = null;
        try {
            writer = ExcelUtil.getWriter();
            //自定义标题别名
            writer.addHeaderAlias("subjectId", "科目编号");
            writer.addHeaderAlias("subjectName", "科目名称");
            // 默认的，未添加alias的属性也会写出，如果想只写出加了别名的字段，可以调用此方法排除之
            writer.setOnlyAlias(true);
            // 一次性写出内容，使用默认样式，强制输出标题
            writer.write(subjectVos, true);
            ZipEntry entry = new ZipEntry("importKemus" + ".xls");
            zipOutputStream.putNextEntry(entry);
            // 写出Excel到目标流
            writer.flush(zipOutputStream);
        } finally {
            // 关闭writer，释放内存
            IoUtil.close(writer);
        }
    }

    /**
     * 导入报名库文件到zip中
     *
     * @param asynBmkFile
     * @param zipOutputStream
     */
    private void assembleBmkToZip(File asynBmkFile, ZipOutputStream zipOutputStream) throws Exception {
        // DBF临时文件路径
        File file = asynBmkFile;
        // DBF临时文件输入流
        InputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(file);

            ZipEntry dbfEntry = new ZipEntry(BMK_PREFIX + BMK_SUFFIX);
            zipOutputStream.putNextEntry(dbfEntry);
            IoUtil.copy(fileInputStream, zipOutputStream);
        } finally {
            IoUtil.close(fileInputStream);
            FileUtil.del(file);
        }
    }

    /**
     * 导入报名库文件到zip中
     *
     * @param asynBmkFileMap
     * @param zipOutputStream
     */
    private void assembleBmkToZip(Map<String, File> asynBmkFileMap, ZipOutputStream zipOutputStream) {
        asynBmkFileMap.forEach((key, file) -> {
            // DBF临时文件输入流
            InputStream fileInputStream = null;
            try {
                fileInputStream = Files.newInputStream(file.toPath());

                ZipEntry dbfEntry = new ZipEntry(key);
                zipOutputStream.putNextEntry(dbfEntry);
                IoUtil.copy(fileInputStream, zipOutputStream);
            } catch (Exception e) {
                log.error("# 导入报名库文件到zip异常", e);
            } finally {
                IoUtil.close(fileInputStream);
                FileUtil.del(file);
            }
        });
    }

    /**
     * 异步生成BmkDbf文件
     *
     * @param studentVos
     * @return
     */
    private Future<File> asynBmkFile(List<StudentVo> studentVos) {
        return ExecutorPoolUtil.getPoolTaskExecutor()
                .submit(() -> {
                    // DBF临时文件路径
                    File file = FileUtil.createTempFile(BMK_PREFIX, BMK_SUFFIX, Boolean.TRUE);
                    file.deleteOnExit();
                    // DBF临时文件输出流
                    OutputStream fileOutputStream = null;
                    try {
                        fileOutputStream = new FileOutputStream(file);
                        Charset charset = Charset.forName("GBK");
                        DBFWriter writer = new DBFWriter(fileOutputStream, charset);
                        writer.setFields(generateBmkFields());

                        // 将数据写进DBF流中
                        studentVos.stream()
                                .map(data -> new Object[]{data.getSubjectId(),
                                        data.getAdmissionNum(),
                                        data.getStudentName(),
                                        data.getIdCard(),
                                        data.getRoomId(),
                                        data.getArea(),
                                        data.getSchoolId()})
                                .forEach(writer::addRecord);
                        // 注意，在这里调用dbf工具关闭流在源码层面是进行一次flush
                        DBFUtils.close(writer);
                    } catch (FileNotFoundException e) {
                        logger.error("# BmkDbf文件生成异常", e);
                    } finally {
                        IoUtil.close(fileOutputStream);
                    }
                    return file;
                });
    }

    /**
     * 异步生成报名库文件
     *
     * @param subjectStudents
     * @return
     */
    private Future<Map<String, File>> asynBmkFile(Map<String, List<StudentVo>> subjectStudents, Map<String, String> subjectMapping) {
        return ExecutorPoolUtil.getPoolTaskExecutor()
                .submit(() -> {
                    return subjectStudents.entrySet()
                            .stream()
                            .map(entry -> {
                                String subjectName = subjectMapping.get(entry.getKey());
                                String fileName = StrUtil.format("{}_{}{}", BMK_PREFIX, subjectName, BMK_SUFFIX);
                                List<StudentVo> studentVos = entry.getValue();

                                // DBF临时文件路径
                                File file = FileUtil.createTempFile(BMK_PREFIX, BMK_SUFFIX, Boolean.TRUE);
                                file.deleteOnExit();
                                // DBF临时文件输出流
                                OutputStream fileOutputStream = null;
                                try {
                                    fileOutputStream = new FileOutputStream(file);
                                    Charset charset = Charset.forName("GBK");
                                    DBFWriter writer = new DBFWriter(fileOutputStream, charset);
                                    writer.setFields(generateBmkFields());

                                    // 将数据写进DBF流中
                                    studentVos.stream()
                                            .map(data -> new Object[]{data.getSubjectId(),
                                                    data.getAdmissionNum(),
                                                    data.getStudentName(),
                                                    data.getIdCard(),
                                                    data.getRoomId(),
                                                    data.getArea(),
                                                    data.getSchoolId()})
                                            .forEach(writer::addRecord);
                                    // 注意，在这里调用dbf工具关闭流在源码层面是进行一次flush
                                    DBFUtils.close(writer);
                                } catch (FileNotFoundException e) {
                                    logger.error("# BmkDbf文件生成异常", e);
                                } finally {
                                    IoUtil.close(fileOutputStream);
                                }
                                return ImmutableMap.<String, File>builder()
                                        .put(fileName, file)
                                        .build();
                            }).map(ImmutableMap::entrySet)
                            .flatMap(Collection::stream)
                            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
                });
    }

    /**
     * 导入客观题成绩文件dbf文件到zip中
     *
     * @param kgtcjDbfFile
     * @param zipOutputStream
     */
    private void assembleKgtcjToZip(File kgtcjDbfFile, ZipOutputStream zipOutputStream) throws Exception {
        // DBF临时文件路径
        File file = kgtcjDbfFile;
        // DBF临时文件输入流
        InputStream fileInputStream = new FileInputStream(file);
        try {
            ZipEntry dbfEntry = new ZipEntry(KGTCJ_PREFIX + KGTCJ_SUFFIX);
            zipOutputStream.putNextEntry(dbfEntry);
            IoUtil.copy(fileInputStream, zipOutputStream);
        } catch (IOException e) {
            log.error("# 导入客观题成绩文件dbf文件到zip中异常", e);
        } finally {
            DBFUtils.close(fileInputStream);
            FileUtil.del(file);
        }
    }

    /**
     * 导入客观题成绩文件dbf文件到zip中
     *
     * @param kgtcjDbfFileMap
     * @param zipOutputStream
     */
    private void assembleKgtcjToZip(Map<String, File> kgtcjDbfFileMap, ZipOutputStream zipOutputStream) {
        kgtcjDbfFileMap.forEach((key, file) -> {
            // DBF临时文件输入流
            InputStream fileInputStream = null;
            try {
                fileInputStream = Files.newInputStream(file.toPath());
                ZipEntry dbfEntry = new ZipEntry(key);
                zipOutputStream.putNextEntry(dbfEntry);
                IoUtil.copy(fileInputStream, zipOutputStream);
            } catch (IOException e) {
                log.error("# 导入客观题成绩文件dbf文件到zip中异常", e);
            } finally {
                IoUtil.close(fileInputStream);
                FileUtil.del(file);
            }
        });
    }

    /**
     * 装配客观题成绩dbf流文件
     *
     * @param objectiveScoreVos
     * @return
     * @throws Exception
     */
    private String assembleKgtcjDbfStream(List<ObjectiveScoreVo> objectiveScoreVos, BiFunction<File, InputStream, String> function) throws Exception {
        if (CollUtil.isEmpty(objectiveScoreVos)) {
            return null;
        }
        // DBF临时文件路径
        File file = null;
        // DBF临时文件输入流
        InputStream fileInputStream = null;
        try {
            Future<File> fileFuture = this.asyncKgtcjDbfFile(objectiveScoreVos);
            file = fileFuture.get();
            fileInputStream = new FileInputStream(file);
            // 这里面操作流最终的输出方式
            return function.apply(file, fileInputStream);
        } finally {
            DBFUtils.close(fileInputStream);
            FileUtil.del(file);
        }
    }

    /**
     * 异步生成KgtcjDbf文件
     *
     * @param objectiveScoreVos
     * @return
     * @throws FileNotFoundException
     */
    private Future<File> asyncKgtcjDbfFile(List<ObjectiveScoreVo> objectiveScoreVos) {
        return ExecutorPoolUtil.getPoolTaskExecutor()
                .submit(() -> {
                    // DBF临时文件路径
                    File file = FileUtil.createTempFile(KGTCJ_PREFIX, KGTCJ_SUFFIX, Boolean.TRUE);
                    file.deleteOnExit();
                    // DBF临时文件输出流
                    OutputStream fileOutputStream = null;
                    try {
                        fileOutputStream = new FileOutputStream(file);
                        Charset charset = Charset.forName("GBK");
                        DBFWriter writer = new DBFWriter(fileOutputStream, charset);
                        writer.setFields(generateKgtcjFields());

                        // 将数据写进DBF流中
                        objectiveScoreVos.stream()
                                .map(data -> new Object[]{data.getSubjectId(),
                                        data.getAdmissionNum(),
                                        data.getStudentName(),
                                        data.getObjectiveScore(),
                                        data.getObjectiveScoreStr(),
                                        data.getObjectiveOmrStr()})
                                .forEach(writer::addRecord);
                        DBFUtils.close(writer);
                    } catch (FileNotFoundException e) {
                        logger.error("# KgtcjDbf文件生成异常", e);
                    } finally {
                        DBFUtils.close(fileOutputStream);
                    }
                    return file;
                });
    }

    /**
     * 异步生成Kgtcj txt文件
     *
     * @param objectiveScoreVoMaps
     * @return
     * @throws FileNotFoundException
     */
    private Future<Map<String, File>> asyncKgtcjTxtFile(Map<String, List<ObjectiveScoreVo>> objectiveScoreVoMaps, Map<String, String> subjectMapping) {
        return ExecutorPoolUtil.getPoolTaskExecutor()
                .submit(() -> {
                    return objectiveScoreVoMaps.entrySet()
                            .stream()
                            .map(entry -> {
                                List<ObjectiveScoreVo> objectiveScoreVos = entry.getValue();

                                String subjectName = subjectMapping.get(entry.getKey());
                                String fileName = StrUtil.format("{}OMR_{}人{}", subjectName, objectiveScoreVos.size(), INTERFACE_SUFFIX);

                                File file = FileUtil.createTempFile(KGTCJ_PREFIX, INTERFACE_SUFFIX, Boolean.TRUE);
                                file.deleteOnExit();
                                BufferedWriter bw = null;
                                try {
                                    bw = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(file.toPath()), Charset.forName("GBK")));

                                    for (ObjectiveScoreVo objectiveScoreVo : objectiveScoreVos) {
                                        String objectiveScoreText = objectiveScoreVo.getAdmissionNum() + SPACE_KEY +
                                                objectiveScoreVo.getStudentName() + SPACE_KEY +
                                                objectiveScoreVo.getMissExamFlag() + SPACE_KEY +
                                                objectiveScoreVo.getPaperTypeCode() + SPACE_KEY +
                                                objectiveScoreVo.getSubjectTypeCode() + SPACE_KEY +
                                                objectiveScoreVo.getObjectiveScore() + SPACE_KEY +
                                                objectiveScoreVo.getObjectiveScoreStr() + SPACE_KEY +
                                                objectiveScoreVo.getObjectiveOmrStr() + SPACE_KEY +
                                                objectiveScoreVo.getMultipleSelectionResult() + SPACE_KEY +
                                                objectiveScoreVo.getPositivePicPath() + SPACE_KEY +
                                                objectiveScoreVo.getBackPicPath() + SPACE_KEY +
                                                objectiveScoreVo.getCardPositivePicPath() + SPACE_KEY +
                                                objectiveScoreVo.getCardBackPicPath() + SPACE_KEY +
                                                objectiveScoreVo.getRollBagNO() + SPACE_KEY +
                                                objectiveScoreVo.getViolationFlag() + SPACE_KEY +
                                                objectiveScoreVo.getEncodeId() + SPACE_KEY +
                                                objectiveScoreVo.getSignUpId();
                                        bw.write(objectiveScoreText);
                                        bw.newLine();
                                    }

                                    IoUtil.flush(bw);
                                } catch (IOException e) {
                                    logger.error("# 异步生成报名库文件异常", e);
                                } finally {
                                    IoUtil.close(bw);
                                }
                                return ImmutableMap.<String, File>builder()
                                        .put(fileName, file)
                                        .build();
                            }).map(ImmutableMap::entrySet)
                            .flatMap(Collection::stream)
                            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
                });
    }

    /**
     * 异步导入题结构文件txt文件生成
     *
     * @param subjectVos
     */
    private Future<Map<String, Future<File>>> asynItemStructureFile(List<SubjectVo> subjectVos) {
        return ExecutorPoolUtil.getPoolTaskExecutor()
                .submit(() -> {
                    // Map<文件名称, Future<File>>
                    Map<String, Future<File>> fileHashMap = Maps.newHashMap();
                    for (SubjectVo subjectVo : subjectVos) {
                        if (CollUtil.isEmpty(subjectVo.getItemVOList())) {
                            continue;
                        }
                        // 异步试题结构文件txt文件生成
                        Future<File> fileFuture = this.asynItemStructureFile(subjectVo);
                        // 文件名称
                        String fileName = "zg_" + subjectVo.getSubjectId() + INTERFACE_SUFFIX;
                        fileHashMap.put(fileName, fileFuture);
                    }
                    return fileHashMap;
                });
    }

    /**
     * 异步导入题结构文件txt文件生成
     *
     * @param subjectVo
     * @return
     */
    private Future<File> asynItemStructureFile(SubjectVo subjectVo) {
        return ExecutorPoolUtil.getPoolTaskExecutor()
                .submit(() -> {
                    // TXT临时文件路径
                    File file = FileUtil.createTempFile(ITEM_STRUCTURE_PREFIX, INTERFACE_SUFFIX, Boolean.TRUE);
                    file.deleteOnExit();
                    BufferedWriter bw = null;
                    try {
                        bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), Charset.forName("GBK")));
                        // 大题数据渲染
                        List<TopicVO> topicVOList = subjectVo.getTopicVOList();
                        if (CollUtil.isNotEmpty(topicVOList)) {
                            for (TopicVO topicVO : topicVOList) {
                                String topicString = topicVO.getSubjectId() + SPACE_KEY +
                                        topicVO.getTopicCode() + SPACE_KEY +
                                        topicVO.getTopicName() + SPACE_KEY +
                                        topicVO.getScore() + SPACE_KEY +
                                        topicVO.getScoreType() + SPACE_KEY +
                                        topicVO.getScoreStep() + SPACE_KEY +
                                        topicVO.getGl();
                                bw.write(topicString);
                                bw.newLine();
                            }
                        }
                        // 输出#号
                        bw.write("#");
                        bw.newLine();

                        // 渲染小题数据
                        List<TopicItemVO> itemVOList = subjectVo.getItemVOList();
                        if (CollUtil.isNotEmpty(itemVOList)) {
                            for (TopicItemVO itemVO : itemVOList) {
                                String itemString = itemVO.getSubjectId() + SPACE_KEY +
                                        itemVO.getTopicId() + SPACE_KEY +
                                        itemVO.getItemId() + SPACE_KEY +
                                        itemVO.getItemName() + SPACE_KEY +
                                        itemVO.getStartScore() + SPACE_KEY +
                                        itemVO.getScore() + SPACE_KEY +
                                        itemVO.getScoreMin() + SPACE_KEY +
                                        itemVO.getScoreStep() + SPACE_KEY +
                                        itemVO.getEnumType();
                                bw.write(itemString);
                                bw.newLine();
                            }
                        }

                        IoUtil.flush(bw);
                    } catch (IOException e) {
                        logger.error("# 导入题结构文件txt文件生成异常", e);
                    } finally {
                        IoUtil.close(bw);
                    }
                    return file;
                });
    }

    /**
     * 导入题结构文件txt文件到zip中
     *
     * @param itemStructureFileFutureMap<文件名称, Future<File>>
     * @param zipOutputStream
     */
    private void assembleItemStructureToZip(Map<String, Future<File>> itemStructureFileFutureMap, ZipOutputStream zipOutputStream) throws Exception {
        if (MapUtil.isEmpty(itemStructureFileFutureMap)) {
            return;
        }
        itemStructureFileFutureMap.forEach((fileName, fileFuture) -> {
            // TXT临时文件路径
            File file = null;
            // TXT临时文件输入流
            InputStream fileInputStream = null;
            try {
                file = fileFuture.get();
                fileInputStream = new FileInputStream(file);

                ZipEntry dbfEntry = new ZipEntry(fileName);
                zipOutputStream.putNextEntry(dbfEntry);
                IoUtil.copy(fileInputStream, zipOutputStream);
            } catch (InterruptedException e) {
                logger.error("# 导入题结构文件txt文件到zip中异常", e);
                Thread.currentThread().interrupt();
            } catch (Exception e) {
                logger.error("# 导入题结构文件txt文件到zip中异常", e);
            } finally {
                IoUtil.close(fileInputStream);
                FileUtil.del(file);
            }
        });
    }

    /**
     * 导入接口文件txt文件到zip中
     *
     * @param projectId       考试ID
     * @param interfaceDocVos
     * @param zipOutputStream
     * @throws Exception
     */
    private void assembleInterfaceToZip(String projectId, List<InterfaceDocVo> interfaceDocVos, ZipOutputStream zipOutputStream) throws Exception {
        if (CollUtil.isEmpty(interfaceDocVos)) {
            return;
        }
        // TXT临时文件路径
        File file = FileUtil.createTempFile(INTERFACE_PREFIX, INTERFACE_SUFFIX, Boolean.TRUE);
        file.deleteOnExit();
        // TXT临时文件输入流
        BufferedWriter bw = null;
        try (InputStream fileInputStream = new FileInputStream(file)) {
            bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), Charset.forName("GBK")));
            for (InterfaceDocVo interfaceDocVo : interfaceDocVos) {
                // 组装图片zip
                this.assembleImageToZip(interfaceDocVo, zipOutputStream);
                String interfaceString = interfaceDocVo.getDefaultValue1() + SPACE_KEY +
                        interfaceDocVo.getAdmissionNum() + SPACE_KEY +
                        interfaceDocVo.getPieceItemId() + SPACE_KEY +
                        interfaceDocVo.getDefaultValue4() + SPACE_KEY +
                        interfaceDocVo.getDefaultValue5() + SPACE_KEY +
                        interfaceDocVo.getAbsolutePath() + SPACE_KEY +
                        interfaceDocVo.getDefaultValue7() + SPACE_KEY +
                        interfaceDocVo.getDefaultValue8() + SPACE_KEY +
                        interfaceDocVo.getDefaultValue9() + SPACE_KEY +
                        interfaceDocVo.getSubjectId() + SPACE_KEY +
                        interfaceDocVo.getRoomId() + SPACE_KEY +
                        interfaceDocVo.getSeatNo() + SPACE_KEY +
                        interfaceDocVo.getDefaultValue13();
                bw.write(interfaceString);
                bw.newLine();
            }
            IoUtil.flush(bw);

            String filename = projectId + DateUtil.format(DateUtil.date(), DatePattern.createFormatter(YYYYMMDDHHMM)) + INTERFACE_SUFFIX;
            ZipEntry dbfEntry = new ZipEntry(filename);
            zipOutputStream.putNextEntry(dbfEntry);
            IoUtil.copy(fileInputStream, zipOutputStream);
        } finally {
            IoUtil.close(bw);
            FileUtil.del(file);
        }
    }

    /**
     * 导入接口文件txt文件到zip中
     *
     * @param zipOutputStream
     * @throws Exception
     */
    private void assembleInterfaceToZip(Map<String, List<InterfaceDocVo>> interfaceDocVoMaps, ZipOutputStream zipOutputStream, Map<String, String> subjectMapping) throws Exception {
        if (CollUtil.isEmpty(interfaceDocVoMaps)) {
            return;
        }
        interfaceDocVoMaps.forEach((key, interfaceDocVos) -> {
            String subjectName = subjectMapping.get(key);
            String fileName = StrUtil.format("{}{}{}", subjectName, DateUtil.format(DateUtil.date(), DatePattern.createFormatter(YYYYMMDDHHMM)), INTERFACE_SUFFIX);

            // TXT临时文件路径
            File file = FileUtil.createTempFile(INTERFACE_PREFIX, INTERFACE_SUFFIX, Boolean.TRUE);
            file.deleteOnExit();
            // TXT临时文件输入流

            BufferedWriter bw = null;
            try (InputStream fileInputStream = Files.newInputStream(file.toPath())) {
                bw = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(file.toPath()), Charset.forName("GBK")));
                for (InterfaceDocVo interfaceDocVo : interfaceDocVos) {
                    // 组装图片zip
                    this.assembleImageToZip(interfaceDocVo, zipOutputStream);
                    String interfaceString = interfaceDocVo.getDefaultValue1() + SPACE_KEY +
                            interfaceDocVo.getAdmissionNum() + SPACE_KEY +
                            interfaceDocVo.getPieceItemId() + SPACE_KEY +
                            interfaceDocVo.getDefaultValue4() + SPACE_KEY +
                            interfaceDocVo.getDefaultValue5() + SPACE_KEY +
                            interfaceDocVo.getAbsolutePath() + SPACE_KEY +
                            interfaceDocVo.getDefaultValue7() + SPACE_KEY +
                            interfaceDocVo.getDefaultValue8() + SPACE_KEY +
                            interfaceDocVo.getDefaultValue9() + SPACE_KEY +
                            interfaceDocVo.getSubjectId() + SPACE_KEY +
                            interfaceDocVo.getRoomId() + SPACE_KEY +
                            interfaceDocVo.getSeatNo() + SPACE_KEY +
                            interfaceDocVo.getDefaultValue13();
                    bw.write(interfaceString);
                    bw.newLine();
                }
                IoUtil.flush(bw);

                ZipEntry dbfEntry = new ZipEntry(fileName);
                zipOutputStream.putNextEntry(dbfEntry);
                IoUtil.copy(fileInputStream, zipOutputStream);
            } catch (Exception e) {
                log.error("# 导入接口文件txt文件到zip异常", e);
            } finally {
                IoUtil.close(bw);
                FileUtil.del(file);
            }
        });
    }

    /**
     * 装配图片到zip包中
     *
     * @param interfaceDocVo
     * @param zipOutputStream
     * @throws Exception
     */
    private void assembleImageToZip(InterfaceDocVo interfaceDocVo, ZipOutputStream zipOutputStream) throws Exception {
        if (ObjectUtil.isEmpty(interfaceDocVo)) {
            return;
        }
        if (ObjectUtil.isEmpty(interfaceDocVo.getAbsoluteImage())) {
            return;
        }
        String imagePath = IMAGE_DIRECTORY + File.separator + interfaceDocVo.getAdmissionNum() + "_" + interfaceDocVo.getImageNumber() + "_" + UUIDUtils.newSortUUID() + ".jpg";
        interfaceDocVo.setAbsolutePath(imagePath);

        File absoluteImageFile = interfaceDocVo.getAbsoluteImage();
        absoluteImageFile.deleteOnExit();
        BufferedInputStream bufferedInputStream = null;
        try {
            bufferedInputStream = FileUtil.getInputStream(absoluteImageFile);
            // 将图片装配到zip中
            ZipEntry entry = new ZipEntry(imagePath);
            zipOutputStream.putNextEntry(entry);
            IoUtil.copy(bufferedInputStream, zipOutputStream);
        } finally {
            IoUtil.close(bufferedInputStream);
            FileUtil.del(absoluteImageFile);
        }
    }

    /**
     * 获取导入报名库文件的字段
     *
     * @return
     */
    public static DBFField[] generateBmkFields() {
        DBFField[] fields = new DBFField[7];

        // 科目编号
        fields[0] = new DBFField();
        fields[0].setName("KEMUID");
        fields[0].setType(DBFDataType.CHARACTER);
        fields[0].setLength(20);

        // 准考证编号
        fields[1] = new DBFField();
        fields[1].setName("ZKZH");
        fields[1].setType(DBFDataType.CHARACTER);
        fields[1].setLength(60);

        // 考生姓名
        fields[2] = new DBFField();
        fields[2].setName("NAME");
        fields[2].setType(DBFDataType.CHARACTER);
        fields[2].setLength(30);

        // 身份证编号
        fields[3] = new DBFField();
        fields[3].setName("SFZH");
        fields[3].setType(DBFDataType.CHARACTER);
        fields[3].setLength(30);


        // 考场编号
        fields[4] = new DBFField();
        fields[4].setName("EXAMROOM");
        fields[4].setType(DBFDataType.CHARACTER);
        fields[4].setLength(20);

        // 区域
        fields[5] = new DBFField();
        fields[5].setName("AREA");
        fields[5].setType(DBFDataType.CHARACTER);
        fields[5].setLength(30);

        // 学校编号
        fields[6] = new DBFField();
        fields[6].setName("SCHOOL");
        fields[6].setType(DBFDataType.CHARACTER);
        fields[6].setLength(20);
        return fields;
    }

    /**
     * 获取导入客观题成绩文件的字段
     *
     * @return
     */
    public static DBFField[] generateKgtcjFields() {
        DBFField[] fields = new DBFField[6];

        // 科目编号
        fields[0] = new DBFField();
        fields[0].setName("KEMUID");
        fields[0].setType(DBFDataType.CHARACTER);
        fields[0].setLength(200);

        // 准考证编号
        fields[1] = new DBFField();
        fields[1].setName("ZKZH");
        fields[1].setType(DBFDataType.CHARACTER);
        fields[1].setLength(200);

        // 考生姓名
        fields[2] = new DBFField();
        fields[2].setName("NAME");
        fields[2].setType(DBFDataType.CHARACTER);
        fields[2].setLength(200);

        // 试卷成绩
        fields[3] = new DBFField();
        fields[3].setName("KGTCJ");
        fields[3].setType(DBFDataType.NUMERIC);
        fields[3].setLength(6);
        fields[3].setDecimalCount(2);

        // 客观题成绩
        fields[4] = new DBFField();
        fields[4].setName("scorestr");
        fields[4].setType(DBFDataType.CHARACTER);
        fields[4].setLength(253);

        //客观题答案
        fields[5] = new DBFField();
        fields[5].setName("omrstr");
        fields[5].setType(DBFDataType.CHARACTER);
        fields[5].setLength(253);

        return fields;
    }

    @Override
    public ProjectVo getProjectMarkStatusByProjectId(String projectId, String officeId) throws Exception {
        if (StringUtils.isEmpty(projectId) || StringUtils.isEmpty(officeId)) {
            return null;
        }
        ProjectVo projectVo = projectMapper.getProjectMarkStatusByProjectId(projectId, officeId);
        if (null == projectVo) {
            return null;
        }
        // 开始就散下载客观题分数的次数
        Integer projectAllZipTimes = projectDataDownLoadRecordService.getProjectDataDownLoadRecordTimes(projectId, officeId, ProjectDownLoadFileTypeEnum.project_all_zip.getType());
        projectVo.setDownLoadZipTime(null != projectAllZipTimes ? projectAllZipTimes : 0);

        // 开始就散下载客观题分数的次数
        Integer obScoresTimes = projectDataDownLoadRecordService.getProjectDataDownLoadRecordTimes(projectId, officeId, ProjectDownLoadFileTypeEnum.project_objective_score.getType());
        projectVo.setDownLoadObScoreTime(null != obScoresTimes ? obScoresTimes : 0);
        return projectVo;
    }

    /**
     * 设置task相关考点考场等数据
     *
     * @param task
     */
    private void extendTask(Task task, StudentExtendDTO studentExtendDTO) {
        // 设置task里的值
        if (null != studentExtendDTO) {
            task.setPlaceId(studentExtendDTO.getPlaceId());
            task.setPlaceName(studentExtendDTO.getPlaceName());
            task.setRoomId(studentExtendDTO.getRoomId());
            task.setRoomName(studentExtendDTO.getRoomName());
            task.setSeatNo(studentExtendDTO.getSeatNo());
            // 考试开始时间
            if (null != studentExtendDTO.getExamBeginDate()) {
                task.setExamStartTime(Date.from(studentExtendDTO.getExamBeginDate().atZone(ZoneId.systemDefault()).toInstant()));
            }
            // 考试结束时间
            if (null != studentExtendDTO.getExamEndDate()) {
                task.setExamEndTime(Date.from(studentExtendDTO.getExamEndDate().atZone(ZoneId.systemDefault()).toInstant()));
            }
            // 考生答题时长
            if (null != studentExtendDTO.getSubjectUseTime()) {
                task.setAnswerTime(new BigDecimal(studentExtendDTO.getSubjectUseTime() / 1000));
            }
        }
        Project project = this.baseMapper.selectById(task.getProjectId());
        // 虚拟考场的设置批次名称和批次id
        if (null != project.getExamClassfication() && CommonEnums.EXAM_CLASSFICATION_1.getCode().equals(project.getExamClassfication())) {
            task.setBatchId(project.getId());
            task.setBatchName(project.getProjectName());
        }
    }


    @Override
    public IPage<TaskPageItemVo> getStudentInfoPage(PageParam<TaskPageItemVo> pageParam, String currUserId, String projectId, String subjectId, String batchId, String placeId, String roomId, String name,
                                                    String examState, String absentState, String order, String sort) {
        QueryWrapper<ProjectSetting> select = new QueryWrapper<>();
        select.lambda().eq(ProjectSetting::getProjectId, projectId);
        ProjectSetting projectSetting = projectSettingMapper.selectOne(select);
        String anonymous = "";
        Set<String> admissionNums = null;
        if (String.valueOf(CommonEnums.ANONYMOUS_1.getCode()).equals(projectSetting.getAnonymous())) {
            anonymous = projectSetting.getAnonymous();
        } else {
            //处理按照名称搜索
            if (!StringUtils.isEmpty(name)) {
                List<Student> students = studentMapper.selectByNameOradmissionNum(name, projectId);
                admissionNums = students.stream().map(e -> e.getAdmissionNum()).collect(Collectors.toSet());
            }
        }

        IPage<TaskPageItemVo> page = pieceScoreMapper.getPieceScorePage(pageParam, currUserId, projectId, subjectId,
                batchId, placeId, roomId, name, examState, absentState, anonymous, admissionNums, order, sort);
        if (null == page || CollectionUtils.isEmpty(page.getRecords())) {
            return page;
        }

        List<TaskPageItemVo> list = page.getRecords();

        Set<String> subjectIds = list.stream().map(e -> e.getSubjectId()).collect(Collectors.toSet());
        QueryWrapper<Subject> subjectWrapper = new QueryWrapper<Subject>();
        subjectWrapper.lambda().in(Subject::getSubjectId, subjectIds);
        List<Subject> subjects = subjectMapper.selectList(subjectWrapper);
        Map<String, String> subjectMap = subjects.stream().collect(Collectors.toMap(t -> t.getSubjectId(), t ->
                t.getSubjectName(), (key1, key2) -> key2));

        QueryWrapper<Student> studentWrapper = new QueryWrapper<Student>();
        studentWrapper.lambda().eq(Student::getProjectId, projectId).in(Student::getSubjectId, subjectIds);
        List<Student> students = studentMapper.selectList(studentWrapper);
        Map<String, Student> studentMap = students.stream().collect(Collectors.toMap(t -> t.getAdmissionNum(), Student ->
                Student, (key1, key2) -> key2));
        //如果是匿名
        for (TaskPageItemVo taskPageItemVo : list) {
            if (CommonEnums.ANONYMOUS_1.getCode().toString().equals(projectSetting.getAnonymous())) {
                taskPageItemVo.setStudentName("***");
            }
            taskPageItemVo.setRejudge(projectSetting.getRejudge());
            if (MapUtils.isNotEmpty(subjectMap) && null != subjectMap.get(taskPageItemVo.getSubjectId())) {
                taskPageItemVo.setSubjectName(subjectMap.get(taskPageItemVo.getSubjectId()));
            }
            if (MapUtils.isNotEmpty(studentMap) && null != studentMap.get(taskPageItemVo.getAdmissionNo())) {
                Student student = studentMap.get(taskPageItemVo.getAdmissionNo());
                taskPageItemVo.setAdmissionNo(student.getAdmissionNum());
                taskPageItemVo.setStudentName(student.getStudentName());
                taskPageItemVo.setSex(student.getSex());
                taskPageItemVo.setAvatar(student.getAvatar());
                taskPageItemVo.setIdCard(student.getIdNo());
                taskPageItemVo.setIdNo(student.getIdNo());
                taskPageItemVo.setTelephone(student.getTelephone());
            }
        }
        page.setRecords(list);
        return page;
    }

    @Override
    public void deleteNotSubjectiveSubject(String projectId) {
        // 查询科目
        List<Subject> subjects = subjectService.selectByProjectId(projectId);
        if (CollectionUtils.isNotEmpty(subjects)) {
            for (Subject subject : subjects) {
                List<PaperItem> paperItemSubjective = paperItemService.getPaperItemSubjective(projectId, subject.getSubjectId());
                if (CollectionUtils.isNotEmpty(paperItemSubjective)) {
                    continue;
                }
                logger.info("##  科目：{}没有主观题", subject.getSubjectName());
                // 为空则继续走,删除同步过来的无效数据
                taskService.remove(new LambdaQueryWrapper<Task>().eq(Task::getProjectId, projectId).eq(Task::getSubjectId, subject.getSubjectId()));
                // 删除piece信息
                List<Piece> pieces = pieceService.getBaseMapper().selectList(new LambdaQueryWrapper<Piece>().eq(Piece::getProjectId, projectId).eq(Piece::getSubjectId, subject.getSubjectId()));
                for (Piece piece : pieces) {
                    pieceItemService.remove(new LambdaQueryWrapper<PieceItem>().eq(PieceItem::getPieceId, piece.getId()));
                    pieceService.removeById(piece.getId());
                }
                // 删除试卷信息
                List<Paper> papers = paperService.getBaseMapper().selectList(new LambdaQueryWrapper<Paper>().eq(Paper::getProjectId, projectId).eq(Paper::getSubjectId, subject.getSubjectId()));
                for (Paper paper : papers) {
                    paperItemService.remove(new LambdaQueryWrapper<PaperItem>().eq(PaperItem::getPaperId, paper.getId()));
                    paperService.removeById(paper.getId());
                }
                // 删除项目科目下的考生答案信息
                studentAnswerService.remove(new LambdaQueryWrapper<StudentAnswer>().eq(StudentAnswer::getProjectId, projectId).eq(StudentAnswer::getSubjectId, subject.getSubjectId()));
                // 删除项目科目下的考生信息
                studentService.remove(new LambdaQueryWrapper<Student>().eq(Student::getProjectId, projectId).eq(Student::getSubjectId, subject.getSubjectId()));
                // 删除科目信息
                subjectService.remove(new LambdaQueryWrapper<Subject>().eq(Subject::getProjectId, projectId).eq(Subject::getSubjectId, subject.getSubjectId()));
                // 删除科目分数信息
                subjectScoreService.remove(new LambdaQueryWrapper<SubjectScore>().eq(SubjectScore::getProjectId, projectId).eq(SubjectScore::getSubjectId, subject.getSubjectId()));
            }
        }
    }

    @Override
    public List<Project> listByTaskId(String timeId) {
        return this.lambdaQuery()
                .eq(BaseEntity::getDelFlag, Boolean.FALSE)
                .eq(Project::getTaskId, timeId)
                .list();
    }

    @Override
    public void deleteProject(String projectId) {
        projectMapper.deletePjConfig(projectId);
        projectMapper.deletePjTask(projectId);
        projectMapper.deletePjStudent(projectId);
        projectMapper.deleteStudentAnswer(projectId);
        projectMapper.deletePjPieceScore(projectId);
        projectMapper.deletePjPieceItem(projectId);
        projectMapper.deletePjPiece(projectId);
        projectMapper.deletePjPaperItem(projectId);
        projectMapper.deletePjPaper(projectId);
        projectMapper.deletePjSubjectScore(projectId);
        projectMapper.deletePjSubject(projectId);
        projectMapper.deletePjProject(projectId);
    }
}
